From 4cf23ed75dd998aff949d6b7b47c15552e6a3253 Mon Sep 17 00:00:00 2001 From: Yuchen Xiao Date: Tue, 7 Apr 2026 23:46:16 -0400 Subject: [PATCH 1/2] feat: add `diffpy.app runmacro <`.dp-in` file>` entry point --- pyproject.toml | 2 +- src/diffpy/apps/app_runmacro.py | 281 ++++++++++++++++++ src/diffpy/apps/apps.py | 57 ++++ src/diffpy/apps/apps_app.py | 33 -- src/diffpy/apps/diffpy_interpreter.py | 228 -------------- src/diffpy/apps/pdfadapter.py | 49 ++- tests/helper.py | 1 - tests/test_pdfadapter.py | 2 +- ..._diffpyinterpreter.py => test_runmacro.py} | 8 +- 9 files changed, 388 insertions(+), 273 deletions(-) create mode 100644 src/diffpy/apps/app_runmacro.py create mode 100644 src/diffpy/apps/apps.py delete mode 100644 src/diffpy/apps/apps_app.py delete mode 100644 src/diffpy/apps/diffpy_interpreter.py rename tests/{test_diffpyinterpreter.py => test_runmacro.py} (91%) diff --git a/pyproject.toml b/pyproject.toml index 4e11f1e..92f67cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [project.scripts] -diffpy-apps = "diffpy.apps.app:main" +"diffpy.app" = "diffpy.apps.apps:main" [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} diff --git a/src/diffpy/apps/app_runmacro.py b/src/diffpy/apps/app_runmacro.py new file mode 100644 index 0000000..5c0d812 --- /dev/null +++ b/src/diffpy/apps/app_runmacro.py @@ -0,0 +1,281 @@ +from pathlib import Path +from collections import OrderedDict +import yaml +from textx import metamodel_from_str + +from diffpy.apps.pdfadapter import PDFAdapter +import inspect + +grammar = r""" +Program: + commands*=Command + variable=VariableBlock +; + +Command: + LoadCommand | SetCommand | CreateCommand | SaveCommand +; + +LoadCommand: + 'load' component=ID name=ID 'from' source=STRING +; + +SetCommand: + 'set' name=ID attribute=ID 'as' value+=Value[eolterm] + | 'set' name=ID 'as' value+=Value[eolterm] +; + +CreateCommand: + 'create' 'equation' 'variables' value+=Value[eolterm] +; + +SaveCommand: + 'save' 'to' source=STRING +; + +VariableBlock: + 'variables:' '---' content=/[\s\S]*?(?=---)/ '---' +; + +Value: + STRICTFLOAT | INT | STRING | RawValue +; + +RawValue: + /[^\s]+/ +; +""" + + +class MacroParser: + def __init__(self): + self.pdfadapter = PDFAdapter() + self.meta_model = metamodel_from_str(grammar) + self.meta_model.register_obj_processors( + { + "SetCommand": self.set_command_processor, + "LoadCommand": self.load_command_processor, + "VariableBlock": self.parameter_block_processor, + "CreateCommand": self.create_command_processor, + "SaveCommand": self.save_command_processor, + } + ) + # key: method_name.argument_name + # value: argument_value + self.inputs = {} + # key: structure name or profile name set in the macro + # value: 'structure' or 'profile' + self.variables = OrderedDict() + + def parse(self, code): + self.meta_model.model_from_str(code) + + def input_as_list(self, key, value): + if key in self.inputs: + if not isinstance(self.inputs[key], list): + self.inputs[key] = [self.inputs[key]] + else: + self.inputs[key].append(value) + else: + if isinstance(value, list): + self.inputs[key] = value + else: + self.inputs[key] = [value] + + def load_command_processor(self, command): + if command.component == "structure": + # TODO: support multiple sturctures input in the future + key = "initialize_structures.structure_paths" + variable = "structure" + elif command.component == "profile": + key = "initialize_profile.profile_path" + variable = "profile" + else: + raise ValueError( + f"Unknown component type: {command.component} " + "Please use 'structure' or 'profile'." + ) + source_path = Path(command.source) + if not source_path.exists(): + raise FileNotFoundError( + f"{command.component} {source_path} not found. " + "Please ensure the path is correct and the file exists." + ) + self.inputs[key] = str(source_path) + self.variables[command.name] = variable + if variable == "structure": + self.input_as_list("initialize_structures.names", command.name) + + def set_command_processor(self, command): + if command.name == "equation": + key = "initialize_contribution.equation" + elif command.name in self.variables: + if self.variables[command.name] == "structure": + if command.attribute == "spacegroup": + key = "initialize_structures.spacegroups" + else: + key = "initialize_structures." + command.attribute + elif self.variables[command.name] == "profile": + key = "initialize_profile." + command.attribute + else: + raise ValueError( + f"Unknown variable type for name: {command.name}. " + "This is an internal error. Please report this issue to " + "the developers." + ) + else: + raise ValueError( + f"Unknown name in set command: {command.name}. " + "Please ensure that it is typed correctly as 'equation' or " + "it matches a previously loaded structure or " + "profile. " + ) + self.input_as_list(key, command.value) + + def parameter_block_processor(self, variable_block): + self.inputs["set_initial_variable_values.variable_name_to_value"] = {} + self.inputs["refine_variables.variable_names"] = [] + parameters = yaml.safe_load(variable_block.content) + if not isinstance(parameters, list): + raise ValueError( + "Parameter block should contain a list of parameters. " + "Please use the following format:\n" + "- param1 # use default initial value\n" + "- param2: initial_value\n" + ) + for item in parameters: + if isinstance(item, str): + self.inputs["refine_variables.variable_names"].append( + item.replace(".", "_") + ) + elif isinstance(item, dict): + pname, pvalue = list(item.items())[0] + self.inputs[ + "set_initial_variable_values.variable_name_to_value" + ][pname.replace(".", "_")] = pvalue + self.inputs["refine_variables.variable_names"].append( + pname.replace(".", "_") + ) + else: + raise ValueError( + "Variables block items are not correctly formatted. " + "Please use the following format:\n" + "- param1 # use default initial value\n" + "- param2: initial_value\n" + ) + + def create_command_processor(self, command): + self.inputs["add_contribution_variables.variable_names"] = ( + command.value + ) + + def save_command_processor(self, command): + self.inputs["save_results.result_path"] = command.source + + def required_args(self, func): + sig = inspect.signature(func) + return [ + name + for name, p in sig.parameters.items() + if p.default is inspect.Parameter.empty + and p.kind + in ( + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + inspect.Parameter.KEYWORD_ONLY, + ) + ] + + def call_pdfadapter_method(self, method_name, function_requirement): + func = getattr(self.pdfadapter, method_name) + required_arguments = self.required_args(func) + arguments = { + key.split(".")[1]: value + for key, value in self.inputs.items() + if key.startswith(method_name) + } + if not all(arg in arguments for arg in required_arguments): + missing_args = [ + arg for arg in required_arguments if arg not in arguments + ] + if function_requirement == "required": + raise ValueError( + "Missing required arguments for function " + f"'{method_name}' {', '.join(missing_args)}. " + "Please provide these arguments in the macro file." + ) + elif function_requirement == "optional": + print( + "Missing required arguments for function " + f"'{method_name}' {', '.join(missing_args)}. " + "This function will be skipped. " + "Please provide these arguments in the macro file " + "to activate this function." + ) + return + func(**arguments) + + def preprocess(self): + methods_to_call = [ + ("initialize_profile", "required"), + ("initialize_structures", "required"), + ("initialize_contribution", "required"), + ("initialize_recipe", "required"), + ("add_contribution_variables", "optional"), + ("set_initial_variable_values", "optional"), + ] + for method in methods_to_call: + self.call_pdfadapter_method(*method) + + def run(self): + methods_to_call = [ + ("refine_variables", "required"), + ("save_results", "optional"), + ] + for method in methods_to_call: + self.call_pdfadapter_method(*method) + return self.pdfadapter.get_results() + + +def runmacro(args): + dpin_path = Path(args.file) + if not dpin_path.exists(): + raise FileNotFoundError( + f"{str(dpin_path)} not found. Please check if this file " + "exists and provide the correct path to it." + ) + dsl_code = dpin_path.read_text() + parser = MacroParser() + parser.parse(dsl_code) + parser.preprocess() + return parser.run() + + +if __name__ == "__main__": + parser = MacroParser() + code = f""" +load structure G1 from "{str(Path(__file__).parents[3] / "tests/data/Ni.cif")}" +load profile exp_ni from "{str(Path(__file__).parents[3] / "tests/data/Ni.gr")}" + +set G1 spacegroup as auto +set exp_ni q_range as 0.1 25 +set exp_ni calculation_range as 1.5 50 0.01 +create equation variables s0 +set equation as "s0*G1" +save to "results.json" + +variables: +--- +- G1.a: 3.52 +- s0: 0.4 +- G1.Uiso_0: 0.005 +- G1.delta2: 2 +- qdamp: 0.04 +- qbroad: 0.02 +--- +""" # noqa: E501 + parser.parse(code) + parser.preprocess() + recipe = parser.pdfadapter.recipe + for pname, param in recipe._parameters.items(): + print(f"{pname}: {param.value}") diff --git a/src/diffpy/apps/apps.py b/src/diffpy/apps/apps.py new file mode 100644 index 0000000..6d1c4d9 --- /dev/null +++ b/src/diffpy/apps/apps.py @@ -0,0 +1,57 @@ +import argparse + +from diffpy.apps.version import __version__ # noqa +from diffpy.apps.app_runmacro import runmacro + + +class DiffpyHelpFormatter(argparse.RawDescriptionHelpFormatter): + """Format subcommands without showing an extra placeholder entry.""" + + def _format_action(self, action): + if isinstance(action, argparse._SubParsersAction): + return "".join( + self._format_action(subaction) + for subaction in self._iter_indented_subactions(action) + ) + return super()._format_action(action) + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.apps", + description=( + "User applications to help with tasks using diffpy packages\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.apps/" + ), + formatter_class=DiffpyHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + apps_parsers = parser.add_subparsers( + title="Available applications", + dest="application", + ) + runmacro_parser = apps_parsers.add_parser( + "runmacro", + help="Run a macro `<.dp-in>` file", + ) + runmacro_parser.add_argument( + "file", + type=str, + help="Path to the `<.dp-in>` macro file to be run", + ) + runmacro_parser.set_defaults(func=runmacro) + args = parser.parse_args() + if args.application is None: + parser.print_help() + else: + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/apps/apps_app.py b/src/diffpy/apps/apps_app.py deleted file mode 100644 index e73331c..0000000 --- a/src/diffpy/apps/apps_app.py +++ /dev/null @@ -1,33 +0,0 @@ -import argparse - -from diffpy.apps.version import __version__ # noqa - - -def main(): - parser = argparse.ArgumentParser( - prog="diffpy.apps", - description=( - "User applications to help with tasks using diffpy packages\n\n" - "For more information, visit: " - "https://github.com/diffpy/diffpy.apps/" - ), - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - - parser.add_argument( - "--version", - action="store_true", - help="Show the program's version number and exit", - ) - - args = parser.parse_args() - - if args.version: - print(f"diffpy.apps {__version__}") - else: - # Default behavior when no arguments are given - parser.print_help() - - -if __name__ == "__main__": - main() diff --git a/src/diffpy/apps/diffpy_interpreter.py b/src/diffpy/apps/diffpy_interpreter.py deleted file mode 100644 index c49d88e..0000000 --- a/src/diffpy/apps/diffpy_interpreter.py +++ /dev/null @@ -1,228 +0,0 @@ -import json -from pathlib import Path - -import yaml -from scipy.optimize import least_squares -from textx import metamodel_from_str - -from diffpy.apps.pdfadapter import PDFAdapter - -grammar = r""" -Program: - commands*=Command - variable=VariableBlock -; - -Command: - LoadCommand | SetCommand | CreateCommand | SaveCommand -; - -LoadCommand: - 'load' component=ID name=ID 'from' source=STRING -; - -SetCommand: - 'set' name=ID attribute=ID 'as' value+=Value[eolterm] - | 'set' name=ID 'as' value+=Value[eolterm] -; - -CreateCommand: - 'create' 'equation' 'variables' value+=Value[eolterm] -; - -SaveCommand: - 'save' 'to' source=STRING -; - -VariableBlock: - 'variables:' '---' content=/[\s\S]*?(?=---)/ '---' -; - -Value: - STRICTFLOAT | INT | STRING | RawValue -; - -RawValue: - /[^\s]+/ -; -""" - - -class DiffpyInterpreter: - def __init__(self): - self.pdfadapter = PDFAdapter() - self.meta_model = metamodel_from_str(grammar) - self.meta_model.register_obj_processors( - { - "SetCommand": self.set_command_processor, - "LoadCommand": self.load_command_processor, - "VariableBlock": self.variable_block_processor, - "CreateCommand": self.create_command_processor, - "SaveCommand": self.save_command_processor, - } - ) - self.inputs = {} - self.profile_name = "" - self.structure_name = ( - "" # TODO: support multiple structures in the future - ) - - def interpret(self, code): - self.meta_model.model_from_str(code) - - def load_command_processor(self, command): - if command.component == "structure": - source_entry = "structure_path" - attribute_name = "structure_name" - elif command.component == "profile": - source_entry = "profile_path" - attribute_name = "profile_name" - else: - raise ValueError( - f"Unknown component type: {command.component} " - "Please use 'structure' or 'profile'." - ) - source_path = Path(command.source) - if not source_path.exists(): - raise FileNotFoundError( - f"{command.component} {source_path} not found. " - "Please ensure the path is correct and the file exists." - ) - self.inputs[source_entry] = str(source_path) - setattr(self, attribute_name, command.name) - - def set_command_processor(self, command): - if "structures_config" not in self.inputs: - self.inputs["structures_config"] = {} - if "profiles_config" not in self.inputs: - self.inputs["profiles_config"] = {} - if command.name in ["equation"]: - self.inputs[command.name] = command.value - elif command.name == self.structure_name: - self.inputs["structures_config"][ - self.structure_name + "_" + command.attribute - ] = command.value - elif command.name == self.profile_name: - self.inputs["profiles_config"][command.attribute] = command.value - else: - raise ValueError( - f"Unknown name in set command: {command.name}. " - "Please ensure it matches a previously loaded structure or " - "profile." - ) - - def variable_block_processor(self, variable_block): - self.inputs["variables"] = [] - self.inputs["initial_values"] = {} - variables = yaml.safe_load(variable_block.content) - if not isinstance(variables, list): - raise ValueError( - "Variables block should contain a list of variables. " - "Please use the following format:\n" - "- var1 # use default initial value\n" - "- var2: initial_value\n" - ) - for item in variables: - if isinstance(item, str): - self.inputs["variables"].append(item.replace(".", "_")) - elif isinstance(item, dict): - pname, pvalue = list(item.items())[0] - self.inputs["variables"].append(pname.replace(".", "_")) - self.inputs["initial_values"][pname.replace(".", "_")] = pvalue - else: - raise ValueError( - "Variables block items are not correctly formatted. " - "Please use the following format:\n" - "- var1 # use default initial value\n" - "- var2: initial_value\n" - ) - - def create_command_processor(self, command): - self.inputs["equation_variable"] = [ - v for v in command.value if isinstance(v, str) - ] - - def save_command_processor(self, command): - self.inputs["result_path"] = command.source - - def configure_adapter(self): - self.pdfadapter.initialize_profile( - self.inputs["profile_path"], **self.inputs["profiles_config"] - ) - spacegroups = self.inputs["structures_config"].get( - f"{self.structure_name}_spacegroup", None - ) - spacegroups = None if spacegroups == ["auto"] else spacegroups - self.pdfadapter.initialize_structures( - [self.inputs["structure_path"]], - run_parallel=True, - spacegroups=spacegroups, - names=[self.structure_name], - ) - self.pdfadapter.initialize_contribution(self.inputs["equation"][0]) - self.pdfadapter.initialize_recipe() - for i in range(len(self.inputs["equation_variable"])): - self.pdfadapter.recipe.addVar( - getattr( - list(self.pdfadapter.recipe._contributions.values())[0], - self.inputs["equation_variable"][i], - ) - ) - self.pdfadapter.set_initial_variable_values( - self.inputs["initial_values"] - ) - - def run(self): - self.pdfadapter.recipe.fix("all") - for var in self.inputs["variables"]: - self.pdfadapter.recipe.free(var) - least_squares( - self.pdfadapter.recipe.residual, self.pdfadapter.recipe.values - ) - if "result_path" in self.inputs: - with open(self.inputs["result_path"], "w") as f: - json.dump(self.pdfadapter.get_results(), f, indent=4) - return self.pdfadapter.get_results() - - def run_app(self, args): - dpin_path = Path(args.input_file) - if not dpin_path.exists(): - raise FileNotFoundError( - f"{str(dpin_path)} not found. Please check if this file " - "exists and provide the correct path to it." - ) - dsl_code = dpin_path.read_text() - self.interpret(dsl_code) - self.configure_adapter() - self.run() - - -if __name__ == "__main__": - interpreter = DiffpyInterpreter() - code = f""" -load structure G1 from "{str(Path(__file__).parents[3] / "tests/data/Ni.cif")}" -load profile exp_ni from "{str(Path(__file__).parents[3] / "tests/data/Ni.gr")}" - -set G1 spacegroup as auto -set exp_ni q_range as 0.1 25 -set exp_ni calculation_range as 1.5 50 0.01 -create equation variables s0 -set equation as "s0*G1" -save to "results.json" - -variables: ---- -- G1.a: 3.52 -- s0: 0.4 -- G1.Uiso_0: 0.005 -- G1.delta2: 2 -- qdamp: 0.04 -- qbroad: 0.02 ---- -""" # noqa: E501 - interpreter.interpret(code) - interpreter.configure_adapter() - recipe = interpreter.pdfadapter.recipe - for pname, param in recipe._parameters.items(): - print(f"{pname}: {param.value}") - print(interpreter.inputs["variables"]) diff --git a/src/diffpy/apps/pdfadapter.py b/src/diffpy/apps/pdfadapter.py index 7855f7b..dc9b850 100644 --- a/src/diffpy/apps/pdfadapter.py +++ b/src/diffpy/apps/pdfadapter.py @@ -1,8 +1,8 @@ +import json import warnings from pathlib import Path import numpy - from diffpy.srfit.fitbase import ( FitContribution, FitRecipe, @@ -12,6 +12,7 @@ from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.structure import constrainAsSpaceGroup from diffpy.structure.parsers import getParser +from scipy.optimize import least_squares class PDFAdapter: @@ -152,6 +153,9 @@ def initialize_structures( structures.append(structure) if not have_spacegroups: spacegroups.append(spacegroup) + else: + if spacegroups[i] == "auto": + spacegroups[i] = spacegroup pdfgenerator = PDFGenerator(name) pdfgenerator.setStructure(structure) if run_parallel: @@ -160,7 +164,7 @@ def initialize_structures( self.spacegroups = spacegroups self.pdfgenerators = pdfgenerators - def initialize_contribution(self, equation_string=None): + def initialize_contribution(self, equation=None): """Create the FitContribution from the loaded profile and generators. @@ -169,19 +173,21 @@ def initialize_contribution(self, equation_string=None): Parameters ---------- - equation_string : str, optional - Equation passed to FitContribution.setEquation. + equation : list of str, optional + The list of a single equation passed to + FitContribution.setEquation. Returns ------- FitContribution The configured contribution object. """ + equation = equation[0] if equation is not None else None contribution = FitContribution("pdfcontribution") contribution.setProfile(self.profile) for pdfgenerator in self.pdfgenerators: contribution.addProfileGenerator(pdfgenerator) - contribution.setEquation(equation_string) + contribution.setEquation(equation) self.contribution = contribution return self.contribution @@ -230,6 +236,35 @@ def initialize_recipe( recipe.fithooks[0].verbose = 0 self.recipe = recipe + def add_contribution_variables(self, variable_names): + """Add contribution parameters to the recipe. + + Parameters + ---------- + variable_names : list of str + The names of the variables to be added to the recipe. + e.g. 's0' for scale factor. + """ + for var_name in variable_names: + self.recipe.addVar( + getattr(self.contribution, var_name), + name=var_name, + fixed=False, + ) + + def refine_variables(self, variable_names): + """Refine the specified variables. + + Parameters + ---------- + variable_names : list of str + The names of the variables to be refined. + """ + self.recipe.fix("all") + for var in variable_names: + self.recipe.free(var) + least_squares(self.recipe.residual, self.recipe.values) + def set_initial_variable_values(self, variable_name_to_value: dict): """Set recipe parameter values by name. @@ -299,3 +334,7 @@ def get_results(self): certain = False results_dict["certain"] = certain return results_dict + + def save_results(self, result_path): + with open(result_path, "w") as f: + json.dump(self.get_results(), f, indent=4) diff --git a/tests/helper.py b/tests/helper.py index b4b1a4f..f557162 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -1,5 +1,4 @@ import numpy as np - from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.structure import constrainAsSpaceGroup diff --git a/tests/test_pdfadapter.py b/tests/test_pdfadapter.py index 4b6c010..3ebdc75 100644 --- a/tests/test_pdfadapter.py +++ b/tests/test_pdfadapter.py @@ -51,7 +51,7 @@ def test_pdfadapter(): str(profile_path), q_range=(0.1, 25), calculation_range=(1.5, 50, 0.01) ) adapter.initialize_structures([str(structure_path)]) - adapter.initialize_contribution(equation_string="s0*G1") + adapter.initialize_contribution(equation=["s0*G1"]) adapter.initialize_recipe() adapter.recipe.addVar(list(adapter.recipe._contributions.values())[0].s0) adapter.set_initial_variable_values(initial_pv_dict) diff --git a/tests/test_diffpyinterpreter.py b/tests/test_runmacro.py similarity index 91% rename from tests/test_diffpyinterpreter.py rename to tests/test_runmacro.py index 307209a..a33e437 100644 --- a/tests/test_diffpyinterpreter.py +++ b/tests/test_runmacro.py @@ -4,7 +4,7 @@ from helper import make_cmi_recipe from scipy.optimize import least_squares -from diffpy.apps.diffpy_interpreter import DiffpyInterpreter +from diffpy.apps.app_runmacro import MacroParser def test_meta_model(): @@ -70,9 +70,9 @@ def test_meta_model(): - qbroad: 0.02 --- """ - diffpy_interpreter = DiffpyInterpreter() - diffpy_interpreter.interpret(diffpy_dsl) - diffpy_interpreter.configure_adapter() + diffpy_interpreter = MacroParser() + diffpy_interpreter.parse(diffpy_dsl) + diffpy_interpreter.preprocess() interpreter_results = diffpy_interpreter.run() for var_name in variables_to_refine: diffpy_value = diffpy_pv_dict[var_name] From c0de59183839d1a93250e52f0834fc67a66f598d Mon Sep 17 00:00:00 2001 From: Yuchen Xiao Date: Tue, 7 Apr 2026 23:52:30 -0400 Subject: [PATCH 2/2] chore: add news --- news/entry-point.rst | 23 +++++++++++++++++++++++ src/diffpy/apps/app_runmacro.py | 7 ++++--- src/diffpy/apps/apps.py | 2 +- src/diffpy/apps/pdfadapter.py | 3 ++- tests/helper.py | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 news/entry-point.rst diff --git a/news/entry-point.rst b/news/entry-point.rst new file mode 100644 index 0000000..8a53fa6 --- /dev/null +++ b/news/entry-point.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``diffpy.app runmacro `` command. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/apps/app_runmacro.py b/src/diffpy/apps/app_runmacro.py index 5c0d812..22096fd 100644 --- a/src/diffpy/apps/app_runmacro.py +++ b/src/diffpy/apps/app_runmacro.py @@ -1,10 +1,11 @@ -from pathlib import Path +import inspect from collections import OrderedDict +from pathlib import Path + import yaml from textx import metamodel_from_str from diffpy.apps.pdfadapter import PDFAdapter -import inspect grammar = r""" Program: @@ -84,7 +85,7 @@ def input_as_list(self, key, value): def load_command_processor(self, command): if command.component == "structure": - # TODO: support multiple sturctures input in the future + # TODO: support multiple structures input in the future key = "initialize_structures.structure_paths" variable = "structure" elif command.component == "profile": diff --git a/src/diffpy/apps/apps.py b/src/diffpy/apps/apps.py index 6d1c4d9..ef5ee44 100644 --- a/src/diffpy/apps/apps.py +++ b/src/diffpy/apps/apps.py @@ -1,7 +1,7 @@ import argparse -from diffpy.apps.version import __version__ # noqa from diffpy.apps.app_runmacro import runmacro +from diffpy.apps.version import __version__ # noqa class DiffpyHelpFormatter(argparse.RawDescriptionHelpFormatter): diff --git a/src/diffpy/apps/pdfadapter.py b/src/diffpy/apps/pdfadapter.py index dc9b850..5ea27ac 100644 --- a/src/diffpy/apps/pdfadapter.py +++ b/src/diffpy/apps/pdfadapter.py @@ -3,6 +3,8 @@ from pathlib import Path import numpy +from scipy.optimize import least_squares + from diffpy.srfit.fitbase import ( FitContribution, FitRecipe, @@ -12,7 +14,6 @@ from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.structure import constrainAsSpaceGroup from diffpy.structure.parsers import getParser -from scipy.optimize import least_squares class PDFAdapter: diff --git a/tests/helper.py b/tests/helper.py index f557162..b4b1a4f 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -1,4 +1,5 @@ import numpy as np + from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.structure import constrainAsSpaceGroup