diffpy.apps 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.
- diffpy/__init__.py +14 -0
- diffpy/apps/__init__.py +23 -0
- diffpy/apps/app_agentify.py +35 -0
- diffpy/apps/app_runmacro.py +298 -0
- diffpy/apps/apps.py +81 -0
- diffpy/apps/pdfadapter.py +341 -0
- diffpy/apps/version.py +26 -0
- diffpy_apps-0.1.0.dist-info/METADATA +178 -0
- diffpy_apps-0.1.0.dist-info/RECORD +14 -0
- diffpy_apps-0.1.0.dist-info/WHEEL +5 -0
- diffpy_apps-0.1.0.dist-info/entry_points.txt +3 -0
- diffpy_apps-0.1.0.dist-info/licenses/AUTHORS.rst +10 -0
- diffpy_apps-0.1.0.dist-info/licenses/LICENSE.rst +29 -0
- diffpy_apps-0.1.0.dist-info/top_level.txt +1 -0
diffpy/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# (c) 2026 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/diffpy/diffpy.apps/graphs/contributors
|
|
11
|
+
#
|
|
12
|
+
# See LICENSE.rst for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
diffpy/apps/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# (c) 2026 diffpy.apps contributors.
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#
|
|
7
|
+
# File coded by: Yuchen Xiao, Simon Billinge and members of the Billinge group.
|
|
8
|
+
#
|
|
9
|
+
# See GitHub contributions for a more detailed list of contributors.
|
|
10
|
+
# https://github.com/diffpy/diffpy.apps/graphs/contributors
|
|
11
|
+
#
|
|
12
|
+
# See LICENSE.rst for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""User applications to help with tasks using diffpy packages."""
|
|
16
|
+
|
|
17
|
+
# package version
|
|
18
|
+
from diffpy.apps.version import __version__ # noqa
|
|
19
|
+
|
|
20
|
+
# silence the pyflakes syntax checker
|
|
21
|
+
assert __version__ or True
|
|
22
|
+
|
|
23
|
+
# End of file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
REPO_URL = "https://github.com/diffpy/cmi-agent-skills"
|
|
7
|
+
DIR_NAME = "cmi-skill"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def agentify(args):
|
|
11
|
+
agent = args.agent
|
|
12
|
+
system_flag = args.system
|
|
13
|
+
if agent == "claude":
|
|
14
|
+
skills_dir = ".claude/skills"
|
|
15
|
+
elif agent == "codex":
|
|
16
|
+
skills_dir = ".codex/skills"
|
|
17
|
+
if system_flag:
|
|
18
|
+
destination = Path().home() / skills_dir / DIR_NAME
|
|
19
|
+
else:
|
|
20
|
+
destination = Path().cwd() / skills_dir / DIR_NAME
|
|
21
|
+
if destination.exists() and not args.update:
|
|
22
|
+
raise FileExistsError(
|
|
23
|
+
f"Agentic skill {DIR_NAME} already exists at {destination}. "
|
|
24
|
+
"To overwrite, pass '--update' flag to update the skill"
|
|
25
|
+
)
|
|
26
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
27
|
+
tmp_path = Path(tmp)
|
|
28
|
+
subprocess.run(
|
|
29
|
+
["git", "clone", REPO_URL, str(tmp_path)],
|
|
30
|
+
check=True,
|
|
31
|
+
)
|
|
32
|
+
if destination.exists():
|
|
33
|
+
shutil.rmtree(destination)
|
|
34
|
+
shutil.copytree(tmp_path / DIR_NAME, destination, dirs_exist_ok=True)
|
|
35
|
+
print(f"Agentic skill {DIR_NAME} has been deployed to {destination}")
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from textx import metamodel_from_str
|
|
7
|
+
|
|
8
|
+
from diffpy.apps.pdfadapter import PDFAdapter
|
|
9
|
+
|
|
10
|
+
grammar = r"""
|
|
11
|
+
Program:
|
|
12
|
+
commands*=Command
|
|
13
|
+
variable=VariableBlock?
|
|
14
|
+
;
|
|
15
|
+
|
|
16
|
+
Command:
|
|
17
|
+
LoadCommand | SetCommand | CreateCommand | SaveCommand
|
|
18
|
+
;
|
|
19
|
+
|
|
20
|
+
LoadCommand:
|
|
21
|
+
'load' component=ID name=ID 'from' source=STRING
|
|
22
|
+
;
|
|
23
|
+
|
|
24
|
+
SetCommand:
|
|
25
|
+
'set' name=ID attribute=ID 'as' value+=Value[eolterm]
|
|
26
|
+
| 'set' name=ID 'as' value+=Value[eolterm]
|
|
27
|
+
;
|
|
28
|
+
|
|
29
|
+
CreateCommand:
|
|
30
|
+
'create' 'equation' 'variables' value+=Value[eolterm]
|
|
31
|
+
;
|
|
32
|
+
|
|
33
|
+
SaveCommand:
|
|
34
|
+
'save' 'to' source=STRING
|
|
35
|
+
;
|
|
36
|
+
|
|
37
|
+
VariableBlock:
|
|
38
|
+
'variables:' '---' content=/[\s\S]*?(?=---)/ '---'
|
|
39
|
+
;
|
|
40
|
+
|
|
41
|
+
Value:
|
|
42
|
+
STRICTFLOAT | INT | STRING | RawValue
|
|
43
|
+
;
|
|
44
|
+
|
|
45
|
+
RawValue:
|
|
46
|
+
/[^\s]+/
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
Comment: /#[^\n]*/;
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MacroParser:
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self.pdfadapter = PDFAdapter()
|
|
57
|
+
self.meta_model = metamodel_from_str(grammar)
|
|
58
|
+
self.meta_model.register_obj_processors(
|
|
59
|
+
{
|
|
60
|
+
"SetCommand": self.set_command_processor,
|
|
61
|
+
"LoadCommand": self.load_command_processor,
|
|
62
|
+
"VariableBlock": self.parameter_block_processor,
|
|
63
|
+
"CreateCommand": self.create_command_processor,
|
|
64
|
+
"SaveCommand": self.save_command_processor,
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
# key: method_name.argument_name
|
|
68
|
+
# value: argument_value
|
|
69
|
+
self.inputs = {}
|
|
70
|
+
# key: structure name or profile name set in the macro
|
|
71
|
+
# value: 'structure' or 'profile'
|
|
72
|
+
self.variables = OrderedDict()
|
|
73
|
+
|
|
74
|
+
def parse(self, code):
|
|
75
|
+
self.meta_model.model_from_str(code)
|
|
76
|
+
|
|
77
|
+
def input_as_list(self, key, value):
|
|
78
|
+
if key in self.inputs:
|
|
79
|
+
if not isinstance(self.inputs[key], list):
|
|
80
|
+
self.inputs[key] = [self.inputs[key]]
|
|
81
|
+
else:
|
|
82
|
+
self.inputs[key].append(value)
|
|
83
|
+
else:
|
|
84
|
+
if isinstance(value, list):
|
|
85
|
+
self.inputs[key] = value
|
|
86
|
+
else:
|
|
87
|
+
self.inputs[key] = [value]
|
|
88
|
+
|
|
89
|
+
def load_command_processor(self, command):
|
|
90
|
+
if command.component == "structure":
|
|
91
|
+
# TODO: support multiple structures input in the future
|
|
92
|
+
if (
|
|
93
|
+
self.inputs.get("initialize_structures.structure_paths")
|
|
94
|
+
is not None
|
|
95
|
+
):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"Multiple structures are not supported by `runmacro` yet. "
|
|
98
|
+
"Please use python script instead."
|
|
99
|
+
)
|
|
100
|
+
key = "initialize_structures.structure_paths"
|
|
101
|
+
variable = "structure"
|
|
102
|
+
elif command.component == "profile":
|
|
103
|
+
if self.inputs.get("initialize_profile.profile_path") is not None:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
"Multiple profiles are not supported by `runmacro`. "
|
|
106
|
+
"Please use python script instead."
|
|
107
|
+
)
|
|
108
|
+
key = "initialize_profile.profile_path"
|
|
109
|
+
variable = "profile"
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"Unknown component type: {command.component} "
|
|
113
|
+
"Please use 'structure' or 'profile'."
|
|
114
|
+
)
|
|
115
|
+
source_path = Path(command.source)
|
|
116
|
+
if not source_path.exists():
|
|
117
|
+
raise FileNotFoundError(
|
|
118
|
+
f"{command.component} {source_path} not found. "
|
|
119
|
+
"Please ensure the path is correct and the file exists."
|
|
120
|
+
)
|
|
121
|
+
self.inputs[key] = str(source_path)
|
|
122
|
+
self.variables[command.name] = variable
|
|
123
|
+
if variable == "structure":
|
|
124
|
+
self.input_as_list("initialize_structures.names", command.name)
|
|
125
|
+
|
|
126
|
+
def set_command_processor(self, command):
|
|
127
|
+
if command.name == "equation":
|
|
128
|
+
key = "initialize_contribution.equation"
|
|
129
|
+
elif command.name in self.variables:
|
|
130
|
+
if self.variables[command.name] == "structure":
|
|
131
|
+
if command.attribute == "spacegroup":
|
|
132
|
+
key = "initialize_structures.spacegroups"
|
|
133
|
+
else:
|
|
134
|
+
key = "initialize_structures." + command.attribute
|
|
135
|
+
elif self.variables[command.name] == "profile":
|
|
136
|
+
key = "initialize_profile." + command.attribute
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"Unknown variable type for name: {command.name}. "
|
|
140
|
+
"This is an internal error. Please report this issue to "
|
|
141
|
+
"the developers."
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Unknown name in set command: {command.name}. "
|
|
146
|
+
"Please ensure that it is typed correctly as 'equation' or "
|
|
147
|
+
"it matches a previously loaded structure or "
|
|
148
|
+
"profile. "
|
|
149
|
+
)
|
|
150
|
+
self.input_as_list(key, command.value)
|
|
151
|
+
|
|
152
|
+
def parameter_block_processor(self, variable_block):
|
|
153
|
+
self.inputs["set_initial_variable_values.variable_name_to_value"] = {}
|
|
154
|
+
self.inputs["refine_variables.variable_names"] = []
|
|
155
|
+
parameters = yaml.safe_load(variable_block.content)
|
|
156
|
+
if not isinstance(parameters, list):
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"Parameter block should contain a list of parameters. "
|
|
159
|
+
"Please use the following format:\n"
|
|
160
|
+
"- param1 # use default initial value\n"
|
|
161
|
+
"- param2: initial_value\n"
|
|
162
|
+
)
|
|
163
|
+
for item in parameters:
|
|
164
|
+
if isinstance(item, str):
|
|
165
|
+
self.inputs["refine_variables.variable_names"].append(
|
|
166
|
+
item.replace(".", "_")
|
|
167
|
+
)
|
|
168
|
+
elif isinstance(item, dict):
|
|
169
|
+
pname, pvalue = list(item.items())[0]
|
|
170
|
+
self.inputs[
|
|
171
|
+
"set_initial_variable_values.variable_name_to_value"
|
|
172
|
+
][pname.replace(".", "_")] = pvalue
|
|
173
|
+
self.inputs["refine_variables.variable_names"].append(
|
|
174
|
+
pname.replace(".", "_")
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
"Variables block items are not correctly formatted. "
|
|
179
|
+
"Please use the following format:\n"
|
|
180
|
+
"- param1 # use default initial value\n"
|
|
181
|
+
"- param2: initial_value\n"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def create_command_processor(self, command):
|
|
185
|
+
self.inputs["add_contribution_variables.variable_names"] = (
|
|
186
|
+
command.value
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def save_command_processor(self, command):
|
|
190
|
+
self.inputs["save_results.result_path"] = command.source
|
|
191
|
+
|
|
192
|
+
def required_args(self, func):
|
|
193
|
+
sig = inspect.signature(func)
|
|
194
|
+
return [
|
|
195
|
+
name
|
|
196
|
+
for name, p in sig.parameters.items()
|
|
197
|
+
if p.default is inspect.Parameter.empty
|
|
198
|
+
and p.kind
|
|
199
|
+
in (
|
|
200
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
201
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
202
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
203
|
+
)
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
def call_pdfadapter_method(self, method_name, function_requirement):
|
|
207
|
+
func = getattr(self.pdfadapter, method_name)
|
|
208
|
+
required_arguments = self.required_args(func)
|
|
209
|
+
arguments = {
|
|
210
|
+
key.split(".")[1]: value
|
|
211
|
+
for key, value in self.inputs.items()
|
|
212
|
+
if key.startswith(method_name)
|
|
213
|
+
}
|
|
214
|
+
if not all(arg in arguments for arg in required_arguments):
|
|
215
|
+
missing_args = [
|
|
216
|
+
arg for arg in required_arguments if arg not in arguments
|
|
217
|
+
]
|
|
218
|
+
if function_requirement == "required":
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"Missing required arguments for function "
|
|
221
|
+
f"'{method_name}' {', '.join(missing_args)}. "
|
|
222
|
+
"Please provide these arguments in the macro file."
|
|
223
|
+
)
|
|
224
|
+
elif function_requirement == "optional":
|
|
225
|
+
print(
|
|
226
|
+
"Missing required arguments for function "
|
|
227
|
+
f"'{method_name}' {', '.join(missing_args)}. "
|
|
228
|
+
"This function will be skipped. "
|
|
229
|
+
"Please provide these arguments in the macro file "
|
|
230
|
+
"to activate this function."
|
|
231
|
+
)
|
|
232
|
+
return
|
|
233
|
+
func(**arguments)
|
|
234
|
+
|
|
235
|
+
def preprocess(self):
|
|
236
|
+
methods_to_call = [
|
|
237
|
+
("initialize_profile", "required"),
|
|
238
|
+
("initialize_structures", "required"),
|
|
239
|
+
("initialize_contribution", "required"),
|
|
240
|
+
("initialize_recipe", "required"),
|
|
241
|
+
("add_contribution_variables", "optional"),
|
|
242
|
+
("set_initial_variable_values", "optional"),
|
|
243
|
+
]
|
|
244
|
+
for method in methods_to_call:
|
|
245
|
+
self.call_pdfadapter_method(*method)
|
|
246
|
+
|
|
247
|
+
def run(self):
|
|
248
|
+
methods_to_call = [
|
|
249
|
+
("refine_variables", "required"),
|
|
250
|
+
("save_results", "optional"),
|
|
251
|
+
]
|
|
252
|
+
for method in methods_to_call:
|
|
253
|
+
self.call_pdfadapter_method(*method)
|
|
254
|
+
return self.pdfadapter.get_results()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def runmacro(args):
|
|
258
|
+
dpin_path = Path(args.file)
|
|
259
|
+
if not dpin_path.exists():
|
|
260
|
+
raise FileNotFoundError(
|
|
261
|
+
f"{str(dpin_path)} not found. Please check if this file "
|
|
262
|
+
"exists and provide the correct path to it."
|
|
263
|
+
)
|
|
264
|
+
dsl_code = dpin_path.read_text()
|
|
265
|
+
parser = MacroParser()
|
|
266
|
+
parser.parse(dsl_code)
|
|
267
|
+
parser.preprocess()
|
|
268
|
+
return parser.run()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if __name__ == "__main__":
|
|
272
|
+
parser = MacroParser()
|
|
273
|
+
code = f"""
|
|
274
|
+
load structure G1 from "{str(Path(__file__).parents[3] / "tests/data/Ni.cif")}"
|
|
275
|
+
load profile exp_ni from "{str(Path(__file__).parents[3] / "tests/data/Ni.gr")}"
|
|
276
|
+
|
|
277
|
+
set G1 spacegroup as auto
|
|
278
|
+
set exp_ni q_range as 0.1 25
|
|
279
|
+
set exp_ni calculation_range as 1.5 50 0.01
|
|
280
|
+
create equation variables s0
|
|
281
|
+
set equation as "s0*G1"
|
|
282
|
+
save to "results.json"
|
|
283
|
+
|
|
284
|
+
variables:
|
|
285
|
+
---
|
|
286
|
+
- G1.a: 3.52
|
|
287
|
+
- s0: 0.4
|
|
288
|
+
- G1.Uiso_0: 0.005
|
|
289
|
+
- G1.delta2: 2
|
|
290
|
+
- qdamp: 0.04
|
|
291
|
+
- qbroad: 0.02
|
|
292
|
+
---
|
|
293
|
+
""" # noqa: E501
|
|
294
|
+
parser.parse(code)
|
|
295
|
+
parser.preprocess()
|
|
296
|
+
recipe = parser.pdfadapter.recipe
|
|
297
|
+
for pname, param in recipe._parameters.items():
|
|
298
|
+
print(f"{pname}: {param.value}")
|
diffpy/apps/apps.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from diffpy.apps.app_agentify import agentify
|
|
4
|
+
from diffpy.apps.app_runmacro import runmacro
|
|
5
|
+
from diffpy.apps.version import __version__ # noqa
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DiffpyHelpFormatter(argparse.RawDescriptionHelpFormatter):
|
|
9
|
+
"""Format subcommands without showing an extra placeholder entry."""
|
|
10
|
+
|
|
11
|
+
def _format_action(self, action):
|
|
12
|
+
if isinstance(action, argparse._SubParsersAction):
|
|
13
|
+
return "".join(
|
|
14
|
+
self._format_action(subaction)
|
|
15
|
+
for subaction in self._iter_indented_subactions(action)
|
|
16
|
+
)
|
|
17
|
+
return super()._format_action(action)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="diffpy.apps",
|
|
23
|
+
description=(
|
|
24
|
+
"User applications to help with tasks using diffpy packages\n\n"
|
|
25
|
+
"For more information, visit: "
|
|
26
|
+
"https://github.com/diffpy/diffpy.apps/"
|
|
27
|
+
),
|
|
28
|
+
formatter_class=DiffpyHelpFormatter,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--version",
|
|
33
|
+
action="version",
|
|
34
|
+
version=f"diffpy.apps {__version__}",
|
|
35
|
+
)
|
|
36
|
+
apps_parsers = parser.add_subparsers(
|
|
37
|
+
title="Available applications",
|
|
38
|
+
dest="application",
|
|
39
|
+
)
|
|
40
|
+
# runmacro application
|
|
41
|
+
runmacro_parser = apps_parsers.add_parser(
|
|
42
|
+
"runmacro",
|
|
43
|
+
help="Run a macro `<.dp-in>` file",
|
|
44
|
+
)
|
|
45
|
+
runmacro_parser.add_argument(
|
|
46
|
+
"file",
|
|
47
|
+
type=str,
|
|
48
|
+
help="Path to the `<.dp-in>` macro file to be run",
|
|
49
|
+
)
|
|
50
|
+
runmacro_parser.set_defaults(func=runmacro)
|
|
51
|
+
# agentify application
|
|
52
|
+
agentify_parser = apps_parsers.add_parser(
|
|
53
|
+
"agentify",
|
|
54
|
+
help="Deploy diffpy.cmi agentic skills in the local environment.",
|
|
55
|
+
)
|
|
56
|
+
agentify_parser.add_argument(
|
|
57
|
+
"--agent",
|
|
58
|
+
help="The agent to use for the agentic skill.",
|
|
59
|
+
default="claude",
|
|
60
|
+
choices=["claude", "codex"],
|
|
61
|
+
)
|
|
62
|
+
agentify_parser.add_argument(
|
|
63
|
+
"--update",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="When set, update the existing agentic skill.",
|
|
66
|
+
)
|
|
67
|
+
agentify_parser.add_argument(
|
|
68
|
+
"--system",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="When set, deploy the agentic skill to the system directory.",
|
|
71
|
+
)
|
|
72
|
+
agentify_parser.set_defaults(func=agentify)
|
|
73
|
+
args = parser.parse_args()
|
|
74
|
+
if args.application is None:
|
|
75
|
+
parser.print_help()
|
|
76
|
+
else:
|
|
77
|
+
args.func(args)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
from scipy.optimize import least_squares
|
|
7
|
+
|
|
8
|
+
from diffpy.srfit.fitbase import (
|
|
9
|
+
FitContribution,
|
|
10
|
+
FitRecipe,
|
|
11
|
+
FitResults,
|
|
12
|
+
Profile,
|
|
13
|
+
)
|
|
14
|
+
from diffpy.srfit.pdf import PDFGenerator, PDFParser
|
|
15
|
+
from diffpy.srfit.structure import constrainAsSpaceGroup
|
|
16
|
+
from diffpy.structure.parsers import getParser
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PDFAdapter:
|
|
20
|
+
"""Build and configure a PDF fitting workflow backed by
|
|
21
|
+
diffpy.srfit.
|
|
22
|
+
|
|
23
|
+
The adapter assembles the profile, structure generators, contribution,
|
|
24
|
+
and recipe objects required for a PDF refinement, then exposes the fit
|
|
25
|
+
results in a JSON-compatible dictionary.
|
|
26
|
+
|
|
27
|
+
Public methods
|
|
28
|
+
--------------
|
|
29
|
+
initialize_profile(profile_path: str, q_range=None, calculation_range=None)
|
|
30
|
+
Load the experimental PDF profile.
|
|
31
|
+
initialize_structures(
|
|
32
|
+
structure_paths: list[str], run_parallel=True, spacegroups=None,
|
|
33
|
+
names=None
|
|
34
|
+
)
|
|
35
|
+
Load structures and create PDFGenerator objects for them.
|
|
36
|
+
initialize_contribution(equation_string=None)
|
|
37
|
+
Create the FitContribution from the loaded profile and generators.
|
|
38
|
+
initialize_recipe()
|
|
39
|
+
Create the FitRecipe for the current contribution.
|
|
40
|
+
set_initial_variable_values(variable_name_to_value: dict)
|
|
41
|
+
Set recipe parameter values by name.
|
|
42
|
+
get_results()
|
|
43
|
+
Return the current fit results as a JSON-compatible dictionary.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def initialize_profile(
|
|
47
|
+
self,
|
|
48
|
+
profile_path: str,
|
|
49
|
+
q_range=None,
|
|
50
|
+
calculation_range=None,
|
|
51
|
+
):
|
|
52
|
+
"""Load the experimental PDF profile.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
profile_path : str
|
|
57
|
+
The path to the experimental PDF profile file.
|
|
58
|
+
q_range : sequence of float, optional
|
|
59
|
+
The two-element sequence containing qmin and qmax. When omitted,
|
|
60
|
+
the values parsed from the profile file are used.
|
|
61
|
+
calculation_range : list or tuple of three floats, optional
|
|
62
|
+
The three-element sequence or mapping defining xmin, xmax, and dx
|
|
63
|
+
for the calculation range. When omitted, the range parsed from the
|
|
64
|
+
profile file is used.
|
|
65
|
+
"""
|
|
66
|
+
profile = Profile()
|
|
67
|
+
parser = PDFParser()
|
|
68
|
+
parser.parseString(Path(profile_path).read_text())
|
|
69
|
+
profile.loadParsedData(parser)
|
|
70
|
+
if q_range is not None:
|
|
71
|
+
profile.meta["qmin"] = q_range[0]
|
|
72
|
+
profile.meta["qmax"] = q_range[1]
|
|
73
|
+
if calculation_range is not None:
|
|
74
|
+
if isinstance(calculation_range, (list, tuple)):
|
|
75
|
+
calculation_range = {
|
|
76
|
+
"xmin": calculation_range[0],
|
|
77
|
+
"xmax": calculation_range[1],
|
|
78
|
+
"dx": calculation_range[2],
|
|
79
|
+
}
|
|
80
|
+
profile.setCalculationRange(**calculation_range)
|
|
81
|
+
self.profile = profile
|
|
82
|
+
|
|
83
|
+
def initialize_structures(
|
|
84
|
+
self,
|
|
85
|
+
structure_paths: list[str],
|
|
86
|
+
run_parallel=True,
|
|
87
|
+
spacegroups=None,
|
|
88
|
+
names=None,
|
|
89
|
+
):
|
|
90
|
+
"""Load structures and create PDFGenerator objects for them.
|
|
91
|
+
|
|
92
|
+
This method should be called after initialize_profile.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
structure_paths : list of str
|
|
97
|
+
The paths to structure files in CIF format.
|
|
98
|
+
run_parallel : bool
|
|
99
|
+
If True, enable generator parallelization when the optional
|
|
100
|
+
dependencies are available.
|
|
101
|
+
spacegroups : list of str or None
|
|
102
|
+
The space groups are inferred from the parsed CIF
|
|
103
|
+
files when available and otherwise default to "P1". They will be
|
|
104
|
+
used to impose symmetry constraints on the structure parameters
|
|
105
|
+
during fitting.
|
|
106
|
+
names : list of str or None
|
|
107
|
+
The names assigned to the generators. Missing names default to
|
|
108
|
+
"G1", "G2", and so on.
|
|
109
|
+
"""
|
|
110
|
+
if isinstance(structure_paths, str):
|
|
111
|
+
structure_paths = [structure_paths]
|
|
112
|
+
structures = []
|
|
113
|
+
have_spacegroups = False
|
|
114
|
+
if spacegroups is not None:
|
|
115
|
+
if len(spacegroups) != len(structure_paths):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"spacegroups list {spacegroups} must match "
|
|
118
|
+
f"structure_paths list {structure_paths}. "
|
|
119
|
+
"Please provide a space group for each structure or set "
|
|
120
|
+
"spacegroups to None to infer them automatically."
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
have_spacegroups = True
|
|
124
|
+
if not have_spacegroups:
|
|
125
|
+
spacegroups = []
|
|
126
|
+
pdfgenerators = []
|
|
127
|
+
if run_parallel:
|
|
128
|
+
try:
|
|
129
|
+
import multiprocessing
|
|
130
|
+
from multiprocessing import Pool
|
|
131
|
+
|
|
132
|
+
import psutil
|
|
133
|
+
|
|
134
|
+
syst_cores = multiprocessing.cpu_count()
|
|
135
|
+
cpu_percent = psutil.cpu_percent()
|
|
136
|
+
avail_cores = numpy.floor(
|
|
137
|
+
(100 - cpu_percent) / (100.0 / syst_cores)
|
|
138
|
+
)
|
|
139
|
+
ncpu = int(numpy.max([1, avail_cores]))
|
|
140
|
+
pool = Pool(processes=ncpu)
|
|
141
|
+
self.pool = pool
|
|
142
|
+
except ImportError:
|
|
143
|
+
warnings.warn(
|
|
144
|
+
"\nYou don't appear to have the necessary packages for "
|
|
145
|
+
"parallelization. Proceeding without parallelization."
|
|
146
|
+
)
|
|
147
|
+
run_parallel = False
|
|
148
|
+
for i, structure_path in enumerate(structure_paths):
|
|
149
|
+
name = names[i] if names and i < len(names) else f"G{i+1}"
|
|
150
|
+
stru_parser = getParser("cif")
|
|
151
|
+
structure = stru_parser.parse(Path(structure_path).read_text())
|
|
152
|
+
sg = getattr(stru_parser, "spacegroup", None)
|
|
153
|
+
spacegroup = sg.short_name if sg is not None else "P1"
|
|
154
|
+
structures.append(structure)
|
|
155
|
+
if not have_spacegroups:
|
|
156
|
+
spacegroups.append(spacegroup)
|
|
157
|
+
else:
|
|
158
|
+
if spacegroups[i] == "auto":
|
|
159
|
+
spacegroups[i] = spacegroup
|
|
160
|
+
pdfgenerator = PDFGenerator(name)
|
|
161
|
+
pdfgenerator.setStructure(structure)
|
|
162
|
+
if run_parallel:
|
|
163
|
+
pdfgenerator.parallel(ncpu=ncpu, mapfunc=self.pool.map)
|
|
164
|
+
pdfgenerators.append(pdfgenerator)
|
|
165
|
+
self.spacegroups = spacegroups
|
|
166
|
+
self.pdfgenerators = pdfgenerators
|
|
167
|
+
|
|
168
|
+
def initialize_contribution(self, equation=None):
|
|
169
|
+
"""Create the FitContribution from the loaded profile and
|
|
170
|
+
generators.
|
|
171
|
+
|
|
172
|
+
This method should be called after initialize_profile and
|
|
173
|
+
initialize_structures.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
equation : list of str, optional
|
|
178
|
+
The list of a single equation passed to
|
|
179
|
+
FitContribution.setEquation.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
FitContribution
|
|
184
|
+
The configured contribution object.
|
|
185
|
+
"""
|
|
186
|
+
equation = equation[0] if equation is not None else None
|
|
187
|
+
contribution = FitContribution("pdfcontribution")
|
|
188
|
+
contribution.setProfile(self.profile)
|
|
189
|
+
for pdfgenerator in self.pdfgenerators:
|
|
190
|
+
contribution.addProfileGenerator(pdfgenerator)
|
|
191
|
+
contribution.setEquation(equation)
|
|
192
|
+
self.contribution = contribution
|
|
193
|
+
return self.contribution
|
|
194
|
+
|
|
195
|
+
def initialize_recipe(
|
|
196
|
+
self,
|
|
197
|
+
):
|
|
198
|
+
"""Create the FitRecipe for the current contribution.
|
|
199
|
+
|
|
200
|
+
This method should be called after initialize_contribution. The
|
|
201
|
+
recipe includes shared qdamp and qbroad variables, per-generator
|
|
202
|
+
delta1 and delta2 variables, and structure parameters
|
|
203
|
+
constrained by the space group.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
recipe = FitRecipe()
|
|
207
|
+
recipe.addContribution(self.contribution)
|
|
208
|
+
qdamp = recipe.newVar("qdamp", fixed=False, value=0.04)
|
|
209
|
+
qbroad = recipe.newVar("qbroad", fixed=False, value=0.02)
|
|
210
|
+
for i, (pdfgenerator, spacegroup) in enumerate(
|
|
211
|
+
zip(self.pdfgenerators, self.spacegroups)
|
|
212
|
+
):
|
|
213
|
+
for pname in [
|
|
214
|
+
"delta1",
|
|
215
|
+
"delta2",
|
|
216
|
+
]:
|
|
217
|
+
par = getattr(pdfgenerator, pname)
|
|
218
|
+
recipe.addVar(
|
|
219
|
+
par, name=f"{pdfgenerator.name}_{pname}", fixed=False
|
|
220
|
+
)
|
|
221
|
+
recipe.constrain(pdfgenerator.qdamp, qdamp)
|
|
222
|
+
recipe.constrain(pdfgenerator.qbroad, qbroad)
|
|
223
|
+
stru_parset = pdfgenerator.phase
|
|
224
|
+
spacegroupparams = constrainAsSpaceGroup(stru_parset, spacegroup)
|
|
225
|
+
for par in spacegroupparams.xyzpars:
|
|
226
|
+
recipe.addVar(
|
|
227
|
+
par, name=f"{pdfgenerator.name}_{par.name}", fixed=False
|
|
228
|
+
)
|
|
229
|
+
for par in spacegroupparams.latpars:
|
|
230
|
+
recipe.addVar(
|
|
231
|
+
par, name=f"{pdfgenerator.name}_{par.name}", fixed=False
|
|
232
|
+
)
|
|
233
|
+
for par in spacegroupparams.adppars:
|
|
234
|
+
recipe.addVar(
|
|
235
|
+
par, name=f"{pdfgenerator.name}_{par.name}", fixed=False
|
|
236
|
+
)
|
|
237
|
+
recipe.fithooks[0].verbose = 0
|
|
238
|
+
self.recipe = recipe
|
|
239
|
+
|
|
240
|
+
def add_contribution_variables(self, variable_names):
|
|
241
|
+
"""Add contribution parameters to the recipe.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
variable_names : list of str
|
|
246
|
+
The names of the variables to be added to the recipe.
|
|
247
|
+
e.g. 's0' for scale factor.
|
|
248
|
+
"""
|
|
249
|
+
for var_name in variable_names:
|
|
250
|
+
self.recipe.addVar(
|
|
251
|
+
getattr(self.contribution, var_name),
|
|
252
|
+
name=var_name,
|
|
253
|
+
fixed=False,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def refine_variables(self, variable_names):
|
|
257
|
+
"""Refine the specified variables.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
variable_names : list of str
|
|
262
|
+
The names of the variables to be refined.
|
|
263
|
+
"""
|
|
264
|
+
self.recipe.fix("all")
|
|
265
|
+
for var in variable_names:
|
|
266
|
+
self.recipe.free(var)
|
|
267
|
+
least_squares(self.recipe.residual, self.recipe.values)
|
|
268
|
+
|
|
269
|
+
def set_initial_variable_values(self, variable_name_to_value: dict):
|
|
270
|
+
"""Set recipe parameter values by name.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
variable_name_to_value : dict
|
|
275
|
+
Mapping from recipe variable names to new values.
|
|
276
|
+
"""
|
|
277
|
+
for vname, vvalue in variable_name_to_value.items():
|
|
278
|
+
self.recipe._parameters[vname].setValue(vvalue)
|
|
279
|
+
|
|
280
|
+
def get_results(self):
|
|
281
|
+
"""Return the current fit results as a JSON-compatible
|
|
282
|
+
dictionary.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
dict
|
|
287
|
+
Residual statistics, variable values, constraints, the covariance
|
|
288
|
+
matrix, and a certainty flag.
|
|
289
|
+
"""
|
|
290
|
+
fit_results = FitResults(self.recipe)
|
|
291
|
+
results_dict = {}
|
|
292
|
+
results_dict["residual"] = fit_results.residual
|
|
293
|
+
results_dict["contributions"] = (
|
|
294
|
+
fit_results.residual - fit_results.penalty
|
|
295
|
+
)
|
|
296
|
+
results_dict["restraints"] = fit_results.penalty
|
|
297
|
+
results_dict["chi2"] = fit_results.chi2
|
|
298
|
+
results_dict["reduced_chi2"] = fit_results.rchi2
|
|
299
|
+
results_dict["rw"] = fit_results.rw
|
|
300
|
+
# variables
|
|
301
|
+
results_dict["variables"] = {}
|
|
302
|
+
for name, val, unc in zip(
|
|
303
|
+
fit_results.varnames, fit_results.varvals, fit_results.varunc
|
|
304
|
+
):
|
|
305
|
+
results_dict["variables"][name] = {
|
|
306
|
+
"value": val,
|
|
307
|
+
"uncertainty": unc,
|
|
308
|
+
}
|
|
309
|
+
# fixed variables
|
|
310
|
+
results_dict["fixed_variables"] = {}
|
|
311
|
+
if fit_results.fixednames is not None:
|
|
312
|
+
for name, val in zip(
|
|
313
|
+
fit_results.fixednames, fit_results.fixedvals
|
|
314
|
+
):
|
|
315
|
+
results_dict["fixed_variables"][name] = {"value": val}
|
|
316
|
+
# constraints
|
|
317
|
+
results_dict["constraints"] = {}
|
|
318
|
+
if fit_results.connames and fit_results.showcon:
|
|
319
|
+
for con in fit_results.conresults.values():
|
|
320
|
+
for i, loc in enumerate(con.conlocs):
|
|
321
|
+
names = [obj.name for obj in loc]
|
|
322
|
+
name = ".".join(names)
|
|
323
|
+
val = con.convals[i]
|
|
324
|
+
unc = con.conuncs[i]
|
|
325
|
+
results_dict["constraints"][name] = {
|
|
326
|
+
"value": val,
|
|
327
|
+
"uncertainty": unc,
|
|
328
|
+
}
|
|
329
|
+
# covariance matrix
|
|
330
|
+
results_dict["covariance_matrix"] = fit_results.cov.tolist()
|
|
331
|
+
# certainty
|
|
332
|
+
certain = True
|
|
333
|
+
for con in fit_results.conresults.values():
|
|
334
|
+
if (con.dy == 1).all():
|
|
335
|
+
certain = False
|
|
336
|
+
results_dict["certain"] = certain
|
|
337
|
+
return results_dict
|
|
338
|
+
|
|
339
|
+
def save_results(self, result_path):
|
|
340
|
+
with open(result_path, "w") as f:
|
|
341
|
+
json.dump(self.get_results(), f, indent=4)
|
diffpy/apps/version.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# (c) 2026 diffpy.apps contributors.
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#
|
|
7
|
+
# File coded by: Yuchen Xiao, Simon Billinge and members of the Billinge group.
|
|
8
|
+
#
|
|
9
|
+
# See GitHub contributions for a more detailed list of contributors.
|
|
10
|
+
# https://github.com/diffpy/diffpy.apps/graphs/contributors # noqa: E501
|
|
11
|
+
#
|
|
12
|
+
# See LICENSE.rst for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""Definition of __version__."""
|
|
16
|
+
|
|
17
|
+
# We do not use the other three variables, but can be added back if needed.
|
|
18
|
+
# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"]
|
|
19
|
+
|
|
20
|
+
# obtain version information
|
|
21
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
__version__ = version("diffpy.apps")
|
|
25
|
+
except PackageNotFoundError:
|
|
26
|
+
__version__ = "unknown"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffpy.apps
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: User applications to help with tasks using diffpy packages
|
|
5
|
+
Author-email: Yuchen Xiao <yx2924@columbia.edu>, Simon Billinge <sbillinge@ucsb.edu>
|
|
6
|
+
Maintainer-email: Yuchen Xiao <yx2924@columbia.edu>, Simon Billinge <sbillinge@ucsb.edu>
|
|
7
|
+
Project-URL: Homepage, https://github.com/diffpy/diffpy.apps/
|
|
8
|
+
Project-URL: Issues, https://github.com/diffpy/diffpy.apps/issues/
|
|
9
|
+
Keywords: diffraction,PDF,X-ray,neutron
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Operating System :: Unix
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
24
|
+
Requires-Python: <3.14,>=3.11
|
|
25
|
+
Description-Content-Type: text/x-rst
|
|
26
|
+
License-File: LICENSE.rst
|
|
27
|
+
License-File: AUTHORS.rst
|
|
28
|
+
Requires-Dist: numpy
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
|Icon| |title|_
|
|
32
|
+
===============
|
|
33
|
+
|
|
34
|
+
.. |title| replace:: diffpy.apps
|
|
35
|
+
.. _title: https://diffpy.github.io/diffpy.apps
|
|
36
|
+
|
|
37
|
+
.. |Icon| image:: https://avatars.githubusercontent.com/diffpy
|
|
38
|
+
:target: https://diffpy.github.io/diffpy.apps
|
|
39
|
+
:height: 100px
|
|
40
|
+
|
|
41
|
+
|PyPI| |Forge| |PythonVersion| |PR|
|
|
42
|
+
|
|
43
|
+
|CI| |Codecov| |Black| |Tracking|
|
|
44
|
+
|
|
45
|
+
.. |Black| image:: https://img.shields.io/badge/code_style-black-black
|
|
46
|
+
:target: https://github.com/psf/black
|
|
47
|
+
|
|
48
|
+
.. |CI| image:: https://github.com/diffpy/diffpy.apps/actions/workflows/matrix-and-codecov-on-merge-to-main.yml/badge.svg
|
|
49
|
+
:target: https://github.com/diffpy/diffpy.apps/actions/workflows/matrix-and-codecov-on-merge-to-main.yml
|
|
50
|
+
|
|
51
|
+
.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.apps/branch/main/graph/badge.svg
|
|
52
|
+
:target: https://codecov.io/gh/diffpy/diffpy.apps
|
|
53
|
+
|
|
54
|
+
.. |Forge| image:: https://img.shields.io/conda/vn/conda-forge/diffpy.apps
|
|
55
|
+
:target: https://anaconda.org/conda-forge/diffpy.apps
|
|
56
|
+
|
|
57
|
+
.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff
|
|
58
|
+
:target: https://github.com/diffpy/diffpy.apps/pulls
|
|
59
|
+
|
|
60
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.apps
|
|
61
|
+
:target: https://pypi.org/project/diffpy.apps/
|
|
62
|
+
|
|
63
|
+
.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.apps
|
|
64
|
+
:target: https://pypi.org/project/diffpy.apps/
|
|
65
|
+
|
|
66
|
+
.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue
|
|
67
|
+
:target: https://github.com/diffpy/diffpy.apps/issues
|
|
68
|
+
|
|
69
|
+
User applications to help with tasks using diffpy packages. Currently it
|
|
70
|
+
contains
|
|
71
|
+
|
|
72
|
+
- `runmacro`: A runner for DiffPy macro files.
|
|
73
|
+
- `agentify`: A deployer for diffpy.cmi agentic skills.
|
|
74
|
+
|
|
75
|
+
For more information about the diffpy.apps library, please consult our `online documentation <https://diffpy.github.io/diffpy.apps>`_.
|
|
76
|
+
|
|
77
|
+
Citation
|
|
78
|
+
--------
|
|
79
|
+
|
|
80
|
+
If you use diffpy.apps in a scientific publication, we would like you to cite this package as
|
|
81
|
+
|
|
82
|
+
diffpy.apps Package, https://github.com/diffpy/diffpy.apps
|
|
83
|
+
|
|
84
|
+
Installation
|
|
85
|
+
------------
|
|
86
|
+
|
|
87
|
+
The preferred method is to use `Miniconda Python
|
|
88
|
+
<https://docs.conda.io/projects/miniconda/en/latest/miniconda-install.html>`_
|
|
89
|
+
and install from the "conda-forge" channel of Conda packages.
|
|
90
|
+
|
|
91
|
+
To add "conda-forge" to the conda channels, run the following in a terminal. ::
|
|
92
|
+
|
|
93
|
+
conda config --add channels conda-forge
|
|
94
|
+
|
|
95
|
+
We want to install our packages in a suitable conda environment.
|
|
96
|
+
The following creates and activates a new environment named ``diffpy.apps_env`` ::
|
|
97
|
+
|
|
98
|
+
conda create -n diffpy.apps_env diffpy.apps python=3.13
|
|
99
|
+
conda activate diffpy.apps_env
|
|
100
|
+
|
|
101
|
+
The output should print the latest version displayed on the badges above.
|
|
102
|
+
|
|
103
|
+
If the above does not work, you can use ``pip`` to download and install the latest release from
|
|
104
|
+
`Python Package Index <https://pypi.python.org>`_.
|
|
105
|
+
|
|
106
|
+
You still need to install some of its dependencies from ``conda`` ::
|
|
107
|
+
|
|
108
|
+
git clone https://github.com/diffpy/diffpy.apps.git
|
|
109
|
+
cd diffpy.apps
|
|
110
|
+
conda activate diffpy.apps_env
|
|
111
|
+
conda install --file requirements/conda.txt
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
To install using ``pip`` into your ``diffpy.apps_env`` environment, type ::
|
|
115
|
+
|
|
116
|
+
pip install diffpy.apps
|
|
117
|
+
|
|
118
|
+
If you prefer to install from sources, after installing the dependencies, obtain the source archive from
|
|
119
|
+
`GitHub <https://github.com/diffpy/diffpy.apps/>`_. Once installed, ``cd`` into your ``diffpy.apps`` directory
|
|
120
|
+
and run the following ::
|
|
121
|
+
|
|
122
|
+
pip install .
|
|
123
|
+
|
|
124
|
+
This package also provides command-line utilities. To check the software has been installed correctly, type ::
|
|
125
|
+
|
|
126
|
+
diffpy.apps --version
|
|
127
|
+
|
|
128
|
+
You can also type the following command to verify the installation. ::
|
|
129
|
+
|
|
130
|
+
python -c "import diffpy.apps; print(diffpy.apps.__version__)"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
To view the basic usage and available commands, type ::
|
|
134
|
+
|
|
135
|
+
diffpy.apps -h
|
|
136
|
+
|
|
137
|
+
Getting Started
|
|
138
|
+
---------------
|
|
139
|
+
|
|
140
|
+
You may consult our `online documentation <https://diffpy.github.io/diffpy.apps>`_ for tutorials and API references.
|
|
141
|
+
|
|
142
|
+
Support and Contribute
|
|
143
|
+
----------------------
|
|
144
|
+
|
|
145
|
+
If you see a bug or want to request a feature, please `report it as an issue <https://github.com/diffpy/diffpy.apps/issues>`_ and/or `submit a fix as a PR <https://github.com/diffpy/diffpy.apps/pulls>`_.
|
|
146
|
+
|
|
147
|
+
Feel free to fork the project and contribute. To install diffpy.apps
|
|
148
|
+
in a development mode, with its sources being directly used by Python
|
|
149
|
+
rather than copied to a package directory, use the following in the root
|
|
150
|
+
directory ::
|
|
151
|
+
|
|
152
|
+
pip install -e .
|
|
153
|
+
|
|
154
|
+
To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit
|
|
155
|
+
hooks.
|
|
156
|
+
|
|
157
|
+
1. Install pre-commit in your working environment by running ``conda install pre-commit``.
|
|
158
|
+
|
|
159
|
+
2. Initialize pre-commit (one time only) ``pre-commit install``.
|
|
160
|
+
|
|
161
|
+
Thereafter your code will be linted by black and isort and checked against flake8 before you can commit.
|
|
162
|
+
If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should
|
|
163
|
+
pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before
|
|
164
|
+
trying to commit again.
|
|
165
|
+
|
|
166
|
+
Improvements and fixes are always appreciated.
|
|
167
|
+
|
|
168
|
+
Before contributing, please read our `Code of Conduct <https://github.com/diffpy/diffpy.apps/blob/main/CODE-OF-CONDUCT.rst>`_.
|
|
169
|
+
|
|
170
|
+
Contact
|
|
171
|
+
-------
|
|
172
|
+
|
|
173
|
+
For more information on diffpy.apps please visit the project `web-page <https://diffpy.github.io/>`_ or email the maintainers ``Yuchen Xiao(yx2924@columbia.edu) and Simon Billinge(sbillinge@ucsb.edu)``.
|
|
174
|
+
|
|
175
|
+
Acknowledgements
|
|
176
|
+
----------------
|
|
177
|
+
|
|
178
|
+
``diffpy.apps`` is built and maintained with `scikit-package <https://scikit-package.github.io/scikit-package/>`_.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
diffpy/__init__.py,sha256=A_tOI_1lETnfUo1dOfJIONN5aofysoAQ_ZGsYxsAfMI,525
|
|
2
|
+
diffpy/apps/__init__.py,sha256=XsuN1OGd0KAO2ultaC3sMepmjS0S29E0DmC7m1iGgJs,720
|
|
3
|
+
diffpy/apps/app_agentify.py,sha256=MiqRXEkFoNGl108ig9iMzmKe1RWS7oBylX-dfAx1M_0,1151
|
|
4
|
+
diffpy/apps/app_runmacro.py,sha256=4I0mOToOcaroRyIDV_vJr7vkCtkPapCp5zhJYWjTbyQ,10144
|
|
5
|
+
diffpy/apps/apps.py,sha256=xJ161akAKLj9ZwdpfYOQ0jgtsABfcq6YaZi_Y3toowc,2394
|
|
6
|
+
diffpy/apps/pdfadapter.py,sha256=HxiROGXmj1I5_gNDx5xrswsNwiDDMFZd5X6H-oS43RE,12732
|
|
7
|
+
diffpy/apps/version.py,sha256=_B5JoKsrDgIoVd7YfbUt_QnZbFto9XFOgZFRYfu0XgA,898
|
|
8
|
+
diffpy_apps-0.1.0.dist-info/licenses/AUTHORS.rst,sha256=FGOWUsPNZ9m4ss1MD_wVMRR4tDpgqQeGnQdtTnq4u64,199
|
|
9
|
+
diffpy_apps-0.1.0.dist-info/licenses/LICENSE.rst,sha256=BnJcVQf_9vCrIUVdJ3LxMD5S3cjQjuiOeyGReFV8bqQ,1533
|
|
10
|
+
diffpy_apps-0.1.0.dist-info/METADATA,sha256=jq6Vy5Cq_pI6L-MRazvddroi4Qt5z_p3ElYKw-VfqSM,6957
|
|
11
|
+
diffpy_apps-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
diffpy_apps-0.1.0.dist-info/entry_points.txt,sha256=JXWRMH9NTZLJrtVkw6hVOrFJjCbR1y71ey7Dnv-40iU,89
|
|
13
|
+
diffpy_apps-0.1.0.dist-info/top_level.txt,sha256=34qLZyi9mxikpHepVDTMHrrNbe8VoOuz3ShYeiWA7KA,7
|
|
14
|
+
diffpy_apps-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, diffpy.apps contributors.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
17
|
+
may be used to endorse or promote products derived from this software
|
|
18
|
+
without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
diffpy
|