diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt index 04b4fcf..70174b7 100644 --- a/.codespell/ignore_words.txt +++ b/.codespell/ignore_words.txt @@ -6,3 +6,6 @@ mater ;; Frobenius norm used in np.linalg.norm fro + +;; "number of input arguments" used in diffpy.srfit.equation.literals.Operator +nin diff --git a/news/refinement.rst b/news/refinement.rst new file mode 100644 index 0000000..052eb7f --- /dev/null +++ b/news/refinement.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add more flexible interface to utilize ``diffpy.srfit``. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/conda.txt b/requirements/conda.txt index 61d392d..e395a4e 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -6,3 +6,4 @@ pyyaml diffpy.srfit diffpy.srreal diffpy.structure +networkx diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15a..ab1429a 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,3 @@ numpy +inline-snapshot +"mcp[cli]" diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py deleted file mode 100644 index 2d7de20..0000000 --- a/src/diffpy/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2026 The Trustees of Columbia University in the City of New York. -# All rights reserved. -# -# File coded by: Billinge Group members and community contributors. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.apps/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## diff --git a/src/diffpy/apps/pdfadapter.py b/src/diffpy/apps/pdfadapter.py index 5ea27ac..dc9b850 100644 --- a/src/diffpy/apps/pdfadapter.py +++ b/src/diffpy/apps/pdfadapter.py @@ -3,8 +3,6 @@ from pathlib import Path import numpy -from scipy.optimize import least_squares - from diffpy.srfit.fitbase import ( FitContribution, FitRecipe, @@ -14,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: diff --git a/src/diffpy/apps/refinebase/__init__.py b/src/diffpy/apps/refinebase/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/diffpy/apps/refinebase/parametric_model.py b/src/diffpy/apps/refinebase/parametric_model.py new file mode 100644 index 0000000..6b892b2 --- /dev/null +++ b/src/diffpy/apps/refinebase/parametric_model.py @@ -0,0 +1,141 @@ +from collections import OrderedDict + +import networkx as nx +import numpy +from diffpy.srfit.equation.literals import Operator, makeOperator +from diffpy.srfit.fitbase import FitContribution, Profile +from diffpy.srfit.pdf.pdfgenerator import PDFGenerator +from diffpy.structure import Structure + + +class ParametricModel: + # TODO: add constraints and restraints method + def __init__(self, name): + self.meta = OrderedDict() + self.name = name + self._contribution = FitContribution(name) + self._graph = nx.DiGraph() + # NOTE: all submodels will share the same profile + self._submodels = [] + old_validate = self._contribution._validate + self._contribution._validate = lambda: self.validate(old_validate) + + def validate(self, old_validate): + # must provide placeholders to pass the validation + if self._contribution.profile is None: + placeholder_profile = Profile() + placeholder_profile.setObservedProfile( + numpy.arange(100), numpy.arange(100) + ) + self.set_profile(placeholder_profile) + if self._contribution._eq is None: + placeholder_equation = makeOperator( + name=self.pdf_generator.name, + symbol="g", + operation=self.pdf_generator.operation, + nin=0, # NOTE: make sure nin=0 and nout=1 is correct + nout=1, + ) + self._contribution.setEquation("g", ns={"g": placeholder_equation}) + old_validate() + + def _construct_parameter_graph(self, parameterset, prefix=""): + parent_name = f"{prefix}{parameterset.name}" + self._graph.add_node(parent_name, parameter=parameterset) + + for par in parameterset._iterManaged(): + child_name = f"{parent_name}.{par.name}" + self._graph.add_node(child_name, parameter=par) + self._graph.add_edge(parent_name, child_name) + if hasattr(par, "_iterManaged"): + self._construct_parameter_graph( + par, + prefix=f"{parent_name}.", + ) + + def register_submodel(self, symbol, model): + """Register a parametric model to the current model.""" + op = model.evaluate + if not isinstance(model.evaluate, Operator): + op = makeOperator( + name=model.name, + symbol=symbol, + operation=model.evaluate, + nin=0, # NOTE: make sure nin=0 and nout=1 is correct + nout=1, + ) + self._contribution._eqfactory.registerOperator(symbol, op) + # Allow iterPars to traverse the submodel's parameters + self._contribution.addParameterSet(model._contribution) + self._submodels.append(model) + + @property + def parameters(self): + return { + par_node_id: self._graph.nodes[par_node_id]["parameter"] + for par_node_id in self._graph.nodes + if self._graph.out_degree(par_node_id) == 0 + } + + def set_profile(self, profile): + self._contribution.setProfile(profile) + if hasattr(self, "pdf_generator"): + self.pdf_generator.setProfile(profile) + for submodel in self._submodels: + submodel.set_profile(profile) + + def set_equation(self, equation_str, ns={}): + self._contribution.setEquation(equation_str, ns=ns) + + def get_equation(self): + return self._contribution.getEquation() + + def add_parameter(self, parameter): + self._contribution.addParameter(parameter) + + def remove_parameter(self, parameter): + self._contribution.removeParameter(parameter) + + def add_parameter_set(self, parameterset): + self._contribution.addParameterSet(parameterset) + + def remove_parameter_set(self, parameterset): + self._contribution.removeParameterSet(parameterset) + + def prepare(self): + self._graph.clear() + self._construct_parameter_graph(self._contribution, prefix="") + + def evaluate(self): + return self._contribution._eq() + + def residual(self): + # TODO: Implement residual calculation for the model + pass + + +class ParametricModelPDF(ParametricModel): + def __init__(self, name, structure: Structure, meta=None): + super().__init__(name=name) + self._initialize_pdf_generator(structure, meta) + + def _initialize_pdf_generator(self, structure: Structure, meta: dict): + self.pdf_generator = PDFGenerator(self.name) + self.pdf_generator.setStructure(structure) + self._contribution.addParameterSet(self.pdf_generator) + self._contribution._RecipeContainer__managed = ( + self.pdf_generator._RecipeContainer__managed + ) + self._contribution._parameters = self.pdf_generator._parameters + + if meta is not None: + self.processMetaData(meta) + + def processMetaData(self, meta: dict = None): + self.pdf_generator.meta.update(meta) + self.pdf_generator.processMetaData() + + def evaluate(self): + if self._contribution.profile is None: + raise ValueError("Profile is not set for the PDF model.") + return self.pdf_generator.operation() diff --git a/src/diffpy/apps/refinebase/refinement_server.py b/src/diffpy/apps/refinebase/refinement_server.py new file mode 100644 index 0000000..70437f5 --- /dev/null +++ b/src/diffpy/apps/refinebase/refinement_server.py @@ -0,0 +1,248 @@ +import uuid +from typing import Annotated + +from mcp.server import MCPServer + +from diffpy.apps.refinebase.refinement_session import RefinementSession + +session = RefinementSession() +mcp = MCPServer("diffpy.apps") + + +@mcp.tool() +async def add_pdf_profile( + profile_path: Annotated[str, "Path to the PDF profile file"], + profile_name: Annotated[str, "Unique name for the profile"] = uuid.uuid4(), +) -> str: + """Add a pdf profile to the refinement session.""" + from diffpy.apps.refinebase.util import ( + get_pdf_profile, + ) + + profile = get_pdf_profile(profile_path) + session.add_profile(profile, profile_name=profile_name) + return f"Profile {profile_name} added successfully." + + +@mcp.tool() +async def add_dat_profile( + profile_path: Annotated[str, "Path to the .dat profile file"], + profile_name: Annotated[str, "Unique name for the profile"] = uuid.uuid4(), +) -> str: + """Add a profile to the refinement session. + + The profile should satisfy that + xarray, yarray = np.loadtxt(profile_path)""" + from diffpy.apps.refinebase.util import ( + get_dat_profile, + ) + + profile = get_dat_profile(profile_path) + session.add_profile(profile, profile_name=profile_name) + return f"Profile {profile_name} added successfully." + + +@mcp.tool() +def add_text_profile( + xarray: Annotated[list, "X-values of the profile"], + yarray: Annotated[list, "Y-values of the profile"], + dx: Annotated[list, "Uncertainties in the x-values"] = None, + dy: Annotated[list, "Uncertainties in the y-values"] = None, + profile_name: Annotated[str, "Unique name for the profile"] = uuid.uuid4(), +) -> str: + """Add a profile to the refinement session. + + The profile is constructed from the provided xarray and yarray data.""" + from diffpy.apps.refinebase.util import ( + get_text_profile, + ) + + profile = get_text_profile(xarray, yarray, dx=dx, dy=dy) + session.add_profile(profile, profile_name=profile_name) + return f"Profile {profile_name} added successfully." + + +@mcp.tool() +async def add_pdf_model( + structure_path: Annotated[str, "Path to the structure file"], + model_name: Annotated[str, "Name of the parametric model"] = uuid.uuid4(), +) -> str: + """Add a PDF parametric model to the refinement session.""" + from diffpy.apps.refinebase.util import get_pdf_model + + pdf_model = get_pdf_model(structure_path=structure_path, name=model_name) + pdf_model.prepare() + session.add_model(pdf_model) + return f"Model {pdf_model.name} added successfully." + + +@mcp.tool() +async def add_equation_model( + equation: Annotated[str, "Equation for the parametric model"], + model_name: Annotated[str, "Name of the parametric model"] = uuid.uuid4(), +) -> str: + """Add an equation-based parametric model to the refinement session.""" + from diffpy.apps.refinebase.parametric_model import ParametricModel + + equation_model = ParametricModel(name=model_name) + equation_model.set_equation(equation) + equation_model.prepare() + session.add_model(equation_model) + return f"Model {equation_model.name} added successfully." + + +@mcp.tool() +async def list_profiles() -> list[str]: + """List all profiles in the refinement session.""" + return [str(profile_id) for profile_id in session.profiles.keys()] + + +@mcp.tool() +async def list_models() -> list[str]: + """List all models in the refinement session.""" + return [str(model_id) for model_id in session.models.keys()] + + +@mcp.tool() +async def set_model_equation( + model_name: Annotated[str, "Name of the parametric model"], + equation: Annotated[str, "Equation to set for the parametric model"], +) -> str: + """Set or change the equation for a specific parametric model.""" + if model_name not in session.models: + raise ValueError(f"Model with ID {model_name} does not exist.") + + model = session.models[model_name] + model.set_equation(equation) + model.prepare() # Re-prepare the model after changing the equation + + return f"Equation for model {model_name} set successfully." + + +@mcp.tool() +async def combine_models( + parent_model_name: Annotated[str, "Name of the parent parametric model"], + child_model_name: Annotated[str, "Name of the child parametric model"], + symbol: Annotated[ + str, "Symbol to use for child model in the parent model's equation" + ], +) -> str: + """ + Combine two parametric models by registering the child to the parent model. + """ + if parent_model_name not in session.models: + raise ValueError( + f"Parent model with ID {parent_model_name} does not exist." + ) + + if child_model_name not in session.models: + raise ValueError( + f"Child model with ID {child_model_name} does not exist." + ) + + parent_model = session.models[parent_model_name] + child_model = session.models[child_model_name] + + parent_model.register_submodel(symbol, child_model) + parent_model.prepare() + + return ( + f"Models {parent_model_name} and " + f"{child_model_name} combined successfully." + ) + + +@mcp.tool() +async def set_model_param_value( + param_name: Annotated[str, "Name of the parameter to set"], + value: Annotated[float, "Value to set for the parameter"], +) -> str: + """ + Set the value of a specific parameter in a parametric model. + """ + from diffpy.apps.refinebase.util import get_variable + + variable = get_variable(session.models, param_name) + variable.setValue(value) + + return f"Parameter '{param_name}' is set to {value}." + + +@mcp.tool() +async def list_model_parameters( + model_name: Annotated[str, "Name of the parametric model"], +) -> str: + """ + List all parameters of a specific parametric model. + """ + if model_name not in session.models: + raise ValueError(f"Model with ID {model_name} does not exist.") + + model = session.models[model_name] + parameters = { + node_id: par.value for node_id, par in model.parameters.items() + } + return f"Parameters for model '{model_name}': {parameters}" + + +@mcp.tool() +async def get_model_value( + model_name: Annotated[str, "Name of the parametric model"], +) -> str: + """ + Get the value of a specific parametric model. + """ + if model_name not in session.models: + raise ValueError(f"Model with ID {model_name} does not exist.") + + model = session.models[model_name] + value = model.evaluate() + + return f"Value for model '{model_name}': {value}" + + +@mcp.tool() +async def refine( + profile_names: Annotated[ + list[str], "List of profile IDs to use in the refinement" + ], + model_names: Annotated[ + list[str], "List of model IDs to use in the refinement" + ], + variable_names: Annotated[list[str], "List of variable names to refine"], + weights: Annotated[list[float], "List of weights for each profile"] = None, + initial_values: Annotated[ + list[float], "List of initial values for each variable" + ] = None, +) -> str: + """ + Perform a refinement using the specified profiles, models, and variables. + """ + # Retrieve profiles and models from session using provided IDs + from diffpy.apps.refinebase.util import get_variable + + profile_objs = [session.profiles[pid] for pid in profile_names] + model_objs = [session.models[mid] for mid in model_names] + variable_objs = [ + get_variable(session.models, var_name) for var_name in variable_names + ] + + # Perform refinement + session.solve( + profile_objs, + model_objs, + variable_objs, + weights=weights, + initial_values=initial_values, + ) + + # Collect refined variable values + refined_values = {var.name: var.value for var in variable_objs} + + return ( + f"Refinement completed successfully. Refined values: {refined_values}" + ) + + +if __name__ == "__main__": + mcp.run() diff --git a/src/diffpy/apps/refinebase/refinement_session.py b/src/diffpy/apps/refinebase/refinement_session.py new file mode 100644 index 0000000..585201d --- /dev/null +++ b/src/diffpy/apps/refinebase/refinement_session.py @@ -0,0 +1,61 @@ +import uuid +from collections import OrderedDict + +import numpy +from diffpy.srfit.fitbase import ( + FitRecipe, + Profile, +) +from scipy.optimize import leastsq + +from diffpy.apps.refinebase.parametric_model import ( + ParametricModel, +) + + +class RefinementSession: + def __init__(self): + self.recipes = OrderedDict() + self.profiles = OrderedDict() + self.models = OrderedDict() + + def add_profile(self, profile: Profile, profile_name: str = None): + if profile in self.profiles.values(): + raise ValueError("Profile already exists in the session.") + if profile_name is None: + profile_name = str(uuid.uuid4()) + self.profiles[profile_name] = profile + + def add_model(self, model: ParametricModel): + if model in self.models.values(): + raise ValueError("Model already exists in the session.") + self.models[model.name] = model + + def solve( + self, + profiles, + models, + variables, + id=uuid.uuid4(), + weights=None, + initial_values=None, + ): + recipe = FitRecipe() + self.recipes[id] = recipe + if weights is None: + weights = numpy.ones(len(profiles)) / len(profiles) + for i in range(len(models)): + models[i].set_profile(profiles[i]) + recipe.addContribution(models[i]._contribution, weight=weights[i]) + # Add variables + if initial_values is not None: + for var, val in zip(variables, initial_values): + var.value = val + for var in variables: + recipe.addVar(var) + # Refine the recipe + recipe.fix("all") + recipe.residual() + for i in range(len(variables)): + recipe.free(variables[i].name) + leastsq(recipe.residual, recipe.getValues()) diff --git a/src/diffpy/apps/refinebase/util.py b/src/diffpy/apps/refinebase/util.py new file mode 100644 index 0000000..2198a92 --- /dev/null +++ b/src/diffpy/apps/refinebase/util.py @@ -0,0 +1,58 @@ +def get_pdf_profile(profile_path: str): + from diffpy.srfit.fitbase import Profile + from diffpy.srfit.pdf import PDFParser + + profile = Profile() + parser = PDFParser() + parser.parseFile(profile_path) + profile.loadParsedData(parser) + return profile + + +def get_dat_profile(profile_path: str): + from diffpy.srfit.fitbase import Profile + + profile = Profile() + profile.loadtxt(profile_path) + + return profile + + +def get_text_profile(xarray, yarray, dx=None, dy=None): + from diffpy.srfit.fitbase import Profile + + profile = Profile() + profile.setObservedProfile(xarray, yarray, dx=dx, dy=dy) + + return profile + + +def get_pdf_model(structure_path: str, name="pdf"): + from diffpy.structure import Structure + + from diffpy.apps.refinebase.parametric_model import ParametricModelPDF + + stru = Structure() + stru.read(structure_path) + pdf_model = ParametricModelPDF(name, structure=stru) + return pdf_model + + +def get_variable(models_dict, variable_name): + objs = variable_name.split(".") + if objs[0] not in models_dict: + raise ValueError(f"Model '{objs[0]}' not found in the session.") + if variable_name not in models_dict[objs[0]].parameters: + raise ValueError( + f"Variable '{variable_name}' not found in the model '{objs[0]}'." + ) + + return models_dict[objs[0]].parameters[variable_name] + + +# if __name__ == "__main__": +# import numpy as np +# xarray = np.linspace(-2*np.pi, 2*np.pi, 400) +# yarray = np.sin(xarray) + 0.05*np.random.normal(size=len(xarray)) +# X = np.stack((xarray,yarray), axis=1) +# np.savetxt("sine.dat",X) diff --git a/tests/conftest.py b/tests/conftest.py index e3b6313..d43dcdf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,3 +17,17 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path + + +@pytest.fixture +def nested_sine_model(): + from diffpy.apps.refinebase.parametric_model import ParametricModel + + model = ParametricModel("main") + submodel = ParametricModel("sub") + submodel.set_equation("a*x") + model.register_submodel("u", submodel) + model.set_equation("A*sin(u)") + submodel.prepare() + model.prepare() + return model, submodel diff --git a/tests/data/sine.dat b/tests/data/sine.dat new file mode 100644 index 0000000..5c4e92c --- /dev/null +++ b/tests/data/sine.dat @@ -0,0 +1,400 @@ +-6.283185307179586232e+00 5.333472993497439391e-02 +-6.251690643985703844e+00 4.730056680876025543e-02 +-6.220195980791820567e+00 6.674570663149917427e-02 +-6.188701317597938178e+00 8.163496149342541752e-02 +-6.157206654404055790e+00 5.765535646179940910e-02 +-6.125711991210173402e+00 1.362339072376442528e-01 +-6.094217328016290125e+00 1.220400021248826683e-01 +-6.062722664822407737e+00 2.071501486063853370e-01 +-6.031228001628525348e+00 2.171193621378014194e-01 +-5.999733338434642960e+00 2.591809174618869283e-01 +-5.968238675240759683e+00 2.440214685406498218e-01 +-5.936744012046877295e+00 3.844927167452896200e-01 +-5.905249348852994906e+00 3.332980129599112384e-01 +-5.873754685659111630e+00 3.730055499203158154e-01 +-5.842260022465229241e+00 4.607970012517633718e-01 +-5.810765359271346853e+00 4.532285005410850998e-01 +-5.779270696077464464e+00 5.173537937900670736e-01 +-5.747776032883581188e+00 5.883332618147742998e-01 +-5.716281369689698799e+00 5.913440547536299441e-01 +-5.684786706495816411e+00 4.963786760438377854e-01 +-5.653292043301933134e+00 6.396698668434632751e-01 +-5.621797380108050746e+00 5.808737866238390390e-01 +-5.590302716914168357e+00 6.266708299351340106e-01 +-5.558808053720285969e+00 6.227004467993523251e-01 +-5.527313390526402692e+00 6.823731484804475000e-01 +-5.495818727332520304e+00 7.340925226123561353e-01 +-5.464324064138637915e+00 7.302709722170439566e-01 +-5.432829400944754639e+00 8.683837400411715812e-01 +-5.401334737750872250e+00 6.895832548205809154e-01 +-5.369840074556989862e+00 8.215986611475818435e-01 +-5.338345411363107473e+00 7.879480256710136299e-01 +-5.306850748169224197e+00 9.595110296491765611e-01 +-5.275356084975341808e+00 7.299891274658213414e-01 +-5.243861421781459420e+00 9.856307651214625265e-01 +-5.212366758587576143e+00 8.012760024963282435e-01 +-5.180872095393693755e+00 9.416275734260856067e-01 +-5.149377432199811366e+00 8.884518200864035542e-01 +-5.117882769005928978e+00 9.531796693250517150e-01 +-5.086388105812045701e+00 9.782957169622655824e-01 +-5.054893442618163313e+00 9.455210356411575257e-01 +-5.023398779424280924e+00 8.706119647183223620e-01 +-4.991904116230397648e+00 9.780865472617736867e-01 +-4.960409453036515259e+00 9.962353893266722160e-01 +-4.928914789842632871e+00 9.637111112136874658e-01 +-4.897420126648750482e+00 9.982173974726372689e-01 +-4.865925463454867206e+00 1.003147631994909750e+00 +-4.834430800260984817e+00 9.512796572746163859e-01 +-4.802936137067102429e+00 1.082386181777095668e+00 +-4.771441473873219152e+00 1.070112366754480071e+00 +-4.739946810679336764e+00 1.041925366211090109e+00 +-4.708452147485454375e+00 1.087654449509526611e+00 +-4.676957484291571987e+00 1.066061239773753666e+00 +-4.645462821097689599e+00 1.019283108439490437e+00 +-4.613968157903806322e+00 1.021109744295398025e+00 +-4.582473494709923933e+00 1.019643704242726878e+00 +-4.550978831516040657e+00 9.851661156523525698e-01 +-4.519484168322158268e+00 1.033390072637789370e+00 +-4.487989505128275880e+00 1.051638981953632701e+00 +-4.456494841934393492e+00 9.517347418449753071e-01 +-4.425000178740511103e+00 9.599397437158793123e-01 +-4.393505515546627826e+00 9.259938294232830103e-01 +-4.362010852352745438e+00 9.456359598737877903e-01 +-4.330516189158862161e+00 8.943839615779231034e-01 +-4.299021525964979773e+00 8.842597584163086610e-01 +-4.267526862771097385e+00 9.310524867043514785e-01 +-4.236032199577214996e+00 8.431128127481050116e-01 +-4.204537536383332608e+00 8.497795747620827678e-01 +-4.173042873189449331e+00 8.772585612073843242e-01 +-4.141548209995566943e+00 7.530639725678698326e-01 +-4.110053546801683666e+00 8.937803155702943592e-01 +-4.078558883607801278e+00 8.264313830386144089e-01 +-4.047064220413918889e+00 8.024375507067316171e-01 +-4.015569557220036501e+00 8.214380978198694283e-01 +-3.984074894026153668e+00 6.904198695129163399e-01 +-3.952580230832270836e+00 7.439012338732706731e-01 +-3.921085567638388447e+00 7.730044315034473312e-01 +-3.889590904444505615e+00 7.712891941726492950e-01 +-3.858096241250623226e+00 6.620484177142149651e-01 +-3.826601578056740394e+00 6.847804714873817478e-01 +-3.795106914862858005e+00 5.852841570004606586e-01 +-3.763612251668975173e+00 5.715312031949316696e-01 +-3.732117588475092340e+00 5.307797104173763714e-01 +-3.700622925281209952e+00 5.846302213621512589e-01 +-3.669128262087327119e+00 5.156872898311184450e-01 +-3.637633598893444731e+00 3.974016196517171640e-01 +-3.606138935699561898e+00 4.805438480014790903e-01 +-3.574644272505679510e+00 3.968475780637474304e-01 +-3.543149609311796677e+00 4.362874534365135948e-01 +-3.511654946117913845e+00 4.343331697068009700e-01 +-3.480160282924031456e+00 3.253504620557266525e-01 +-3.448665619730148624e+00 3.197758477717250747e-01 +-3.417170956536266235e+00 2.139248532312771067e-01 +-3.385676293342383403e+00 2.671251026291971975e-01 +-3.354181630148501014e+00 1.407863298190564216e-01 +-3.322686966954618182e+00 1.498456323271817325e-01 +-3.291192303760735793e+00 1.011040675029006297e-01 +-3.259697640566852961e+00 1.749477783973979739e-01 +-3.228202977372970128e+00 1.902819554604764096e-01 +-3.196708314179087740e+00 9.513929312934064908e-03 +-3.165213650985204907e+00 7.285066003464360507e-04 +-3.133718987791322519e+00 -8.361559701509049813e-02 +-3.102224324597439686e+00 5.297876588104493550e-02 +-3.070729661403557298e+00 -3.124925641987919694e-02 +-3.039234998209674465e+00 -6.705718346773389960e-02 +-3.007740335015792077e+00 -2.240713315568368391e-01 +-2.976245671821909244e+00 -1.727570161241142321e-01 +-2.944751008628026412e+00 -9.408741018694728730e-02 +-2.913256345434144023e+00 -2.424823907448965254e-01 +-2.881761682240261191e+00 -2.062160158105544316e-01 +-2.850267019046378802e+00 -2.837988747138451839e-01 +-2.818772355852495970e+00 -2.992285332985984980e-01 +-2.787277692658613582e+00 -2.882399490139626597e-01 +-2.755783029464730749e+00 -3.747294518534884378e-01 +-2.724288366270847916e+00 -4.074455775937312896e-01 +-2.692793703076965528e+00 -5.176146618181154402e-01 +-2.661299039883082695e+00 -4.701573418210351907e-01 +-2.629804376689200307e+00 -5.386867638254433377e-01 +-2.598309713495317474e+00 -4.994965794035008955e-01 +-2.566815050301435086e+00 -5.893586583325615136e-01 +-2.535320387107552254e+00 -5.402587202801102384e-01 +-2.503825723913669421e+00 -6.001532378759411035e-01 +-2.472331060719787033e+00 -6.524688559125335630e-01 +-2.440836397525904200e+00 -6.848915691610645284e-01 +-2.409341734332021812e+00 -6.405415348312248902e-01 +-2.377847071138138979e+00 -6.203595609073114803e-01 +-2.346352407944256591e+00 -7.554890400576492748e-01 +-2.314857744750373758e+00 -7.393459699815611019e-01 +-2.283363081556491370e+00 -7.431365063588601938e-01 +-2.251868418362608537e+00 -7.501553583981681594e-01 +-2.220373755168726149e+00 -6.756894603145571221e-01 +-2.188879091974842872e+00 -8.295146705420044508e-01 +-2.157384428780960484e+00 -7.560202667948484612e-01 +-2.125889765587078095e+00 -8.545510958578339533e-01 +-2.094395102393195707e+00 -8.535476106542244645e-01 +-2.062900439199312430e+00 -8.930160561088548166e-01 +-2.031405776005430042e+00 -9.095864437610129416e-01 +-1.999911112811547653e+00 -8.170980662101963166e-01 +-1.968416449617664377e+00 -9.259730579734212208e-01 +-1.936921786423781988e+00 -8.806209237765607600e-01 +-1.905427123229899600e+00 -9.087432800260908383e-01 +-1.873932460036017211e+00 -8.762467885556992631e-01 +-1.842437796842133935e+00 -9.176280691792784161e-01 +-1.810943133648251546e+00 -9.552143480940763487e-01 +-1.779448470454369158e+00 -1.005882970938458243e+00 +-1.747953807260486769e+00 -1.030090866293738250e+00 +-1.716459144066603493e+00 -1.035693889029885462e+00 +-1.684964480872721104e+00 -9.796592853136956158e-01 +-1.653469817678838716e+00 -1.037862979948626752e+00 +-1.621975154484955439e+00 -9.481858771511948447e-01 +-1.590480491291073051e+00 -9.636366654935387688e-01 +-1.558985828097190662e+00 -1.011184886111144543e+00 +-1.527491164903308274e+00 -1.078614094350218711e+00 +-1.495996501709424997e+00 -9.060519205623029926e-01 +-1.464501838515542609e+00 -1.002655387054679492e+00 +-1.433007175321660220e+00 -9.568249554002589141e-01 +-1.401512512127776944e+00 -9.284896397939189638e-01 +-1.370017848933894555e+00 -1.068426804300812050e+00 +-1.338523185740012167e+00 -9.814679801764907285e-01 +-1.307028522546129778e+00 -9.707156705056556589e-01 +-1.275533859352246502e+00 -9.567295825744793758e-01 +-1.244039196158364113e+00 -9.545800343040824476e-01 +-1.212544532964481725e+00 -9.689973339127266883e-01 +-1.181049869770598448e+00 -9.171643615137812233e-01 +-1.149555206576716060e+00 -7.945647407509961457e-01 +-1.118060543382833671e+00 -9.312590718056790173e-01 +-1.086565880188951283e+00 -9.451529682252668429e-01 +-1.055071216995068006e+00 -8.619259436726246504e-01 +-1.023576553801185618e+00 -9.134326796273872784e-01 +-9.920818906073032295e-01 -9.041659185529236087e-01 +-9.605872274134199529e-01 -8.474550531269914311e-01 +-9.290925642195375644e-01 -7.846865575776393875e-01 +-8.975979010256551760e-01 -8.265441754958452458e-01 +-8.661032378317727876e-01 -7.503379633083022693e-01 +-8.346085746378895109e-01 -7.946294753173039904e-01 +-8.031139114440071225e-01 -7.580543671107451420e-01 +-7.716192482501247341e-01 -7.528739666945439835e-01 +-7.401245850562414574e-01 -6.840920389074485186e-01 +-7.086299218623590690e-01 -5.898370127083051306e-01 +-6.771352586684766806e-01 -6.130114298254869531e-01 +-6.456405954745942921e-01 -5.421582738519427069e-01 +-6.141459322807110155e-01 -6.175603883643896452e-01 +-5.826512690868286271e-01 -5.178780251342394170e-01 +-5.511566058929462386e-01 -5.095682253637823234e-01 +-5.196619426990638502e-01 -5.087502303738271614e-01 +-4.881672795051805736e-01 -5.612261688386659397e-01 +-4.566726163112981851e-01 -3.909741827782163281e-01 +-4.251779531174157967e-01 -4.357892929826272521e-01 +-3.936832899235325200e-01 -4.409168765862667150e-01 +-3.621886267296501316e-01 -3.518254685009435390e-01 +-3.306939635357677432e-01 -3.436355721076164715e-01 +-2.991993003418853547e-01 -3.178658071503189797e-01 +-2.677046371480020781e-01 -2.480188690981060817e-01 +-2.362099739541196897e-01 -2.132367809444414286e-01 +-2.047153107602373012e-01 -1.805130274843314842e-01 +-1.732206475663540246e-01 -1.288680730985389544e-01 +-1.417259843724716362e-01 -9.058323720170644022e-02 +-1.102313211785892477e-01 -1.762372598909556776e-01 +-7.873665798470685928e-02 -1.031319045245991245e-01 +-4.724199479082358266e-02 -2.248723402791667075e-02 +-1.574733159694119422e-02 -1.019248259629703182e-02 +1.574733159694119422e-02 5.561015125613280596e-02 +4.724199479082447084e-02 2.850678047274404076e-02 +7.873665798470685928e-02 2.999745151604019111e-02 +1.102313211785892477e-01 1.561400734143876257e-01 +1.417259843724716362e-01 1.302267918949870373e-01 +1.732206475663549128e-01 6.785045455458592334e-02 +2.047153107602373012e-01 2.669477195929434443e-01 +2.362099739541196897e-01 1.842806727012889778e-01 +2.677046371480020781e-01 3.113115434177701202e-01 +2.991993003418853547e-01 2.822754874795110891e-01 +3.306939635357677432e-01 3.365286976034461697e-01 +3.621886267296501316e-01 4.297720577737871306e-01 +3.936832899235334082e-01 4.504576882792186154e-01 +4.251779531174157967e-01 4.257790570354693660e-01 +4.566726163112981851e-01 4.056662603318181293e-01 +4.881672795051805736e-01 5.192140315466554634e-01 +5.196619426990638502e-01 4.726952791730072434e-01 +5.511566058929462386e-01 4.956804196399551277e-01 +5.826512690868286271e-01 5.107578824303367071e-01 +6.141459322807119037e-01 5.482023251793258778e-01 +6.456405954745942921e-01 6.153580261081638136e-01 +6.771352586684766806e-01 5.543029255111912024e-01 +7.086299218623590690e-01 7.088344557009651492e-01 +7.401245850562423456e-01 6.356653778257231879e-01 +7.716192482501247341e-01 7.673458159133867973e-01 +8.031139114440071225e-01 7.066354583780416476e-01 +8.346085746378903991e-01 7.381698728238748064e-01 +8.661032378317727876e-01 7.498746647116933950e-01 +8.975979010256551760e-01 7.165366479557023771e-01 +9.290925642195375644e-01 7.382664416274424823e-01 +9.605872274134208411e-01 8.848588319225931809e-01 +9.920818906073032295e-01 7.955472227369922988e-01 +1.023576553801185618e+00 8.447340369606414212e-01 +1.055071216995068895e+00 8.889494243020082953e-01 +1.086565880188951283e+00 8.448711049043226007e-01 +1.118060543382833671e+00 8.939085720020327752e-01 +1.149555206576716060e+00 8.843083314823503294e-01 +1.181049869770599337e+00 9.515426278408282146e-01 +1.212544532964481725e+00 9.517631033849265343e-01 +1.244039196158364113e+00 8.445403868231657896e-01 +1.275533859352247390e+00 9.784882824996637973e-01 +1.307028522546129778e+00 9.357540430059432257e-01 +1.338523185740012167e+00 1.060796725445710909e+00 +1.370017848933894555e+00 9.246950959108836132e-01 +1.401512512127777832e+00 9.968238652066258032e-01 +1.433007175321660220e+00 9.735599894579468616e-01 +1.464501838515542609e+00 1.010035590115179627e+00 +1.495996501709424997e+00 1.009557336962188634e+00 +1.527491164903308274e+00 9.414509604964800271e-01 +1.558985828097190662e+00 9.706555159935927879e-01 +1.590480491291073051e+00 9.043408568724520258e-01 +1.621975154484956327e+00 1.038821399165152037e+00 +1.653469817678838716e+00 1.075344626837139828e+00 +1.684964480872721104e+00 1.013189559680832241e+00 +1.716459144066603493e+00 9.994075970669294984e-01 +1.747953807260486769e+00 8.798250886605673005e-01 +1.779448470454369158e+00 1.015192233030990154e+00 +1.810943133648251546e+00 9.631822954691046101e-01 +1.842437796842133935e+00 9.264165894676001933e-01 +1.873932460036016323e+00 9.483629352159634873e-01 +1.905427123229900488e+00 9.584441944685537784e-01 +1.936921786423782876e+00 8.110467073191865994e-01 +1.968416449617665265e+00 9.741318652872379413e-01 +1.999911112811547653e+00 8.513519706650380936e-01 +2.031405776005430042e+00 8.363049973814026261e-01 +2.062900439199312430e+00 8.550778795685389788e-01 +2.094395102393194819e+00 8.327541830147691115e-01 +2.125889765587078983e+00 9.226856006582023761e-01 +2.157384428780961372e+00 7.992997631081057985e-01 +2.188879091974843760e+00 8.480535044363670671e-01 +2.220373755168726149e+00 7.976456999502447021e-01 +2.251868418362608537e+00 7.975898775105769722e-01 +2.283363081556490926e+00 8.050987402228190204e-01 +2.314857744750373314e+00 7.520701257850432864e-01 +2.346352407944257479e+00 6.983811898701800525e-01 +2.377847071138139867e+00 7.970811344087292971e-01 +2.409341734332022256e+00 5.813226284302609459e-01 +2.440836397525904644e+00 6.134028296153083426e-01 +2.472331060719787033e+00 6.146775162628060896e-01 +2.503825723913669421e+00 6.166536127641077947e-01 +2.535320387107551809e+00 5.860653128988175808e-01 +2.566815050301435974e+00 6.099760012967703204e-01 +2.598309713495318363e+00 4.064563575340121027e-01 +2.629804376689200751e+00 5.044822483109848710e-01 +2.661299039883083140e+00 4.794187621766899388e-01 +2.692793703076965528e+00 3.892280066576866981e-01 +2.724288366270847916e+00 4.494834440666407582e-01 +2.755783029464730305e+00 3.692565771893440707e-01 +2.787277692658612693e+00 3.638689943321363196e-01 +2.818772355852496858e+00 3.090041453847930941e-01 +2.850267019046379247e+00 3.199056922043522766e-01 +2.881761682240261635e+00 1.420298953349984350e-01 +2.913256345434144023e+00 1.934074762371242318e-01 +2.944751008628026412e+00 1.896192717716490561e-01 +2.976245671821908800e+00 1.849620414727209949e-01 +3.007740335015791189e+00 1.473428426390076540e-01 +3.039234998209675354e+00 5.772470185919462149e-03 +3.070729661403557742e+00 8.670928917959888627e-02 +3.102224324597440130e+00 1.505944256909951817e-01 +3.133718987791322519e+00 1.057205355507669434e-02 +3.165213650985204907e+00 -1.961223285869023725e-02 +3.196708314179087296e+00 -1.074376846044653444e-01 +3.228202977372969684e+00 -4.469753991353946077e-02 +3.259697640566853849e+00 -8.557136958347436040e-02 +3.291192303760736237e+00 -2.092066282696974922e-01 +3.322686966954618626e+00 -2.627579973998567242e-01 +3.354181630148501014e+00 -2.149854955278473201e-01 +3.385676293342383403e+00 -1.911385859942967558e-01 +3.417170956536265791e+00 -2.972566413894532356e-01 +3.448665619730148180e+00 -1.993232814249967233e-01 +3.480160282924032344e+00 -2.616826036489552920e-01 +3.511654946117914733e+00 -2.960827278840391097e-01 +3.543149609311797121e+00 -4.117795647667891745e-01 +3.574644272505679510e+00 -4.162677964300542932e-01 +3.606138935699561898e+00 -3.927339896663378238e-01 +3.637633598893444287e+00 -5.022086973125871046e-01 +3.669128262087326675e+00 -4.883150076528400829e-01 +3.700622925281210840e+00 -5.875789176611229125e-01 +3.732117588475093228e+00 -5.412766498594321352e-01 +3.763612251668975617e+00 -5.386774388369347522e-01 +3.795106914862858005e+00 -6.668850275372711911e-01 +3.826601578056740394e+00 -5.917011555561028091e-01 +3.858096241250622782e+00 -5.871050830140525090e-01 +3.889590904444505171e+00 -7.434780946484742525e-01 +3.921085567638389335e+00 -6.896959418477949244e-01 +3.952580230832271724e+00 -7.820370931495217448e-01 +3.984074894026154112e+00 -7.244114971520748680e-01 +4.015569557220036501e+00 -7.586192837693910285e-01 +4.047064220413918889e+00 -8.665734694141173922e-01 +4.078558883607801278e+00 -7.588937316227160057e-01 +4.110053546801683666e+00 -8.668686906153582061e-01 +4.141548209995567831e+00 -9.034050818378567271e-01 +4.173042873189450219e+00 -8.549632716610970906e-01 +4.204537536383332608e+00 -8.099097720426193803e-01 +4.236032199577214996e+00 -8.371986062495411218e-01 +4.267526862771097385e+00 -9.298874922784227115e-01 +4.299021525964979773e+00 -8.950890249792161635e-01 +4.330516189158862161e+00 -9.811384722945486470e-01 +4.362010852352746326e+00 -9.711891618582448871e-01 +4.393505515546628715e+00 -9.383757903140135248e-01 +4.425000178740511103e+00 -1.000729863724425517e+00 +4.456494841934393492e+00 -1.001826600982660631e+00 +4.487989505128275880e+00 -9.817740824869456429e-01 +4.519484168322158268e+00 -9.146847790507851572e-01 +4.550978831516040657e+00 -1.126770422349620215e+00 +4.582473494709924822e+00 -9.607875710439345296e-01 +4.613968157903807210e+00 -9.696696335892329710e-01 +4.645462821097689599e+00 -1.006070120377763333e+00 +4.676957484291571987e+00 -9.521118417468951156e-01 +4.708452147485454375e+00 -1.039690433541499814e+00 +4.739946810679336764e+00 -9.850761455793340948e-01 +4.771441473873219152e+00 -1.003017940098269056e+00 +4.802936137067103317e+00 -9.756527171870794657e-01 +4.834430800260985706e+00 -1.032874492433806246e+00 +4.865925463454868094e+00 -1.016765811326304281e+00 +4.897420126648750482e+00 -1.036694520869491765e+00 +4.928914789842632871e+00 -9.725114451188217046e-01 +4.960409453036515259e+00 -9.588546622770659500e-01 +4.991904116230397648e+00 -1.052174923539183737e+00 +5.023398779424280036e+00 -9.797463478785696234e-01 +5.054893442618164201e+00 -9.009895922564135073e-01 +5.086388105812046589e+00 -9.258864012489034234e-01 +5.117882769005928978e+00 -9.720017644361897968e-01 +5.149377432199811366e+00 -8.964146762085127840e-01 +5.180872095393693755e+00 -8.733433671649337704e-01 +5.212366758587576143e+00 -8.394766771199186906e-01 +5.243861421781458532e+00 -8.536185191492570157e-01 +5.275356084975342696e+00 -7.684525468952877469e-01 +5.306850748169225085e+00 -7.915153807401620334e-01 +5.338345411363107473e+00 -8.037784602129141032e-01 +5.369840074556989862e+00 -8.074571249585872357e-01 +5.401334737750872250e+00 -7.935669005816983201e-01 +5.432829400944754639e+00 -7.185997545450047985e-01 +5.464324064138637027e+00 -6.733472820114593249e-01 +5.495818727332521192e+00 -6.944689019800011076e-01 +5.527313390526403580e+00 -7.180489238550267217e-01 +5.558808053720285969e+00 -6.509581779709366911e-01 +5.590302716914168357e+00 -5.932740892008087075e-01 +5.621797380108050746e+00 -6.228024299124416130e-01 +5.653292043301933134e+00 -6.411136926038654149e-01 +5.684786706495815523e+00 -6.083455050526439534e-01 +5.716281369689699687e+00 -5.972604330508973769e-01 +5.747776032883582076e+00 -4.425046155799902681e-01 +5.779270696077464464e+00 -6.078718480593998663e-01 +5.810765359271346853e+00 -3.767735728047209021e-01 +5.842260022465229241e+00 -4.979039355957451951e-01 +5.873754685659111630e+00 -3.677547380621239981e-01 +5.905249348852994018e+00 -2.818193350864402480e-01 +5.936744012046878183e+00 -3.446315350032238545e-01 +5.968238675240760571e+00 -2.703450873786260589e-01 +5.999733338434642960e+00 -3.136916165827080549e-01 +6.031228001628525348e+00 -3.296620922232299522e-01 +6.062722664822407737e+00 -2.042509801240406375e-01 +6.094217328016290125e+00 -1.754604529873591279e-01 +6.125711991210172513e+00 -1.776470618976437599e-01 +6.157206654404056678e+00 -9.610723412669471000e-02 +6.188701317597939067e+00 -1.138715211751845091e-01 +6.220195980791821455e+00 -9.664494503363602051e-02 +6.251690643985703844e+00 3.625998729126720527e-02 +6.283185307179586232e+00 6.640668213601869443e-03 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_parametric_model.py b/tests/test_parametric_model.py new file mode 100644 index 0000000..ee1335a --- /dev/null +++ b/tests/test_parametric_model.py @@ -0,0 +1,48 @@ +import numpy +import pytest + + +def test_parametric_model_graph(nested_sine_model): + model, submodel = nested_sine_model + # C1: Create a nested parametric model + # Expect the graph to be constructed correctly + expected_parameters = ["main.A", "main.sub.a", "main.sub.x"] + actual_parameters = list(model.parameters.keys()) + assert set(expected_parameters) == set(actual_parameters) + expected_nodes = ["main", "main.A", "main.sub", "main.sub.a", "main.sub.x"] + actual_nodes = list(model._graph.nodes) + assert set(expected_nodes).issubset(set(actual_nodes)) + expected_edges = [ + ("main", "main.A"), + ("main", "main.sub"), + ("main.sub", "main.sub.a"), + ("main.sub", "main.sub.x"), + ] + actual_edges = list(model._graph.edges) + assert set(expected_edges) == set(actual_edges) + + +def test_parametric_model_parameter_access(nested_sine_model): + # C1: Create a nested sine parametric model + # Expect to models to share the same parameter obj + model, submodel = nested_sine_model + assert model.parameters["main.sub.a"] is submodel.parameters["sub.a"] + + +@pytest.mark.parametrize( + "A, a, x, expected", + [ + (1.0, 1.0, numpy.pi / 2, 1.0), + (2.0, 2.0, numpy.pi / 4, 2.0), + (1.0, 1.0, numpy.pi, 0.0), + ], +) +def test_parametric_model_evaluation(nested_sine_model, A, a, x, expected): + # C1: Create a nested sine parametric model + # Expect the model to evaluate correctly + model, submodel = nested_sine_model + model.parameters["main.A"].value = A + submodel.parameters["sub.a"].value = a + submodel.parameters["sub.x"].value = x + actual = model.evaluate() + assert numpy.isclose(actual, expected, rtol=1e-6) diff --git a/tests/test_refinement_server.py b/tests/test_refinement_server.py new file mode 100644 index 0000000..e803f7e --- /dev/null +++ b/tests/test_refinement_server.py @@ -0,0 +1,58 @@ +import numpy +import pytest +from mcp import Client + +from diffpy.apps.refinebase.refinement_server import mcp +from diffpy.apps.refinebase.util import get_variable + + +@pytest.fixture +def anyio_backend(): + return "asyncio" + + +@pytest.mark.anyio +async def test_refine_sine(): + # C1: Set up the MCP client and do a sine refinement + # Expect all objs are created and refined successfully + from diffpy.apps.refinebase.refinement_server import session + + async with Client(mcp, raise_exceptions=True) as mcp_client: + await mcp_client.call_tool( + "add_dat_profile", + { + "profile_name": "sine_profile", + "profile_path": "tests/data/sine.dat", + }, + ) + assert "sine_profile" in session.profiles + await mcp_client.call_tool( + "add_equation_model", + { + "model_name": "sine_model", + "equation": "A*sin(x)", + }, + ) + assert "sine_model" in session.models + await mcp_client.call_tool( + "set_model_param_value", + { + "param_name": "sine_model.A", + "value": 0.8, + }, + ) + variable_A = get_variable(session.models, "sine_model.A") + expected_value = 0.8 + actual_value = variable_A.value + assert actual_value == expected_value + await mcp_client.call_tool( + "refine", + { + "profile_names": ["sine_profile"], + "model_names": ["sine_model"], + "variable_names": ["sine_model.A"], + }, + ) + expected_value = 1.0 # The expected value of A after refinement + actual_value = variable_A.value + assert numpy.isclose(actual_value, expected_value, rtol=0.2) diff --git a/tests/test_refinement_session.py b/tests/test_refinement_session.py new file mode 100644 index 0000000..e73e652 --- /dev/null +++ b/tests/test_refinement_session.py @@ -0,0 +1,75 @@ +import numpy +from diffpy.srfit.fitbase import ( + Profile, +) +from diffpy.srfit.pdf import PDFParser +from diffpy.structure import Structure + +from diffpy.apps.refinebase.parametric_model import ( + ParametricModel, + ParametricModelPDF, +) +from diffpy.apps.refinebase.refinement_session import RefinementSession + + +def test_refine_sine(): + # C1: Refinement session without additional calculator or functions + session = RefinementSession() + xobs = numpy.linspace(-numpy.pi, numpy.pi, 100) + yobs = numpy.sin(xobs) + 1e-2 * numpy.random.normal(size=xobs.shape) + sine_profile = Profile() + sine_profile.setObservedProfile(xobs, yobs) + sine_model = ParametricModel("sine_model") + sine_model.set_equation("A*sin(a*x)") + sine_model.prepare() + session.solve( + profiles=[sine_profile], + models=[sine_model], + variables=[ + sine_model.parameters["sine_model.A"], + sine_model.parameters["sine_model.a"], + ], + initial_values=[0.5, 0.5], + ) + assert numpy.isclose( + sine_model.parameters["sine_model.A"].value, + 1.0, + rtol=1e-2, + ) + assert numpy.isclose( + sine_model.parameters["sine_model.a"].value, + 1.0, + rtol=1e-2, + ) + + +def test_refine_ni(): + # C1: Refinement session with one PDFCalculator + profile_path = "tests/data/Ni.gr" + profile = Profile() + parser = PDFParser() + parser.parseFile(profile_path) + profile.loadParsedData(parser) + profile.setCalculationRange(xmax=20) + stru = Structure() + structure_path = "tests/data/Ni.cif" + stru.read(structure_path) + + pdf_model = ParametricModelPDF("pdf", structure=stru, meta=profile.meta) + pdf_model.prepare() + ni_model = ParametricModel("ni_model") + ni_model.register_submodel("g", pdf_model) + ni_model.set_equation("s*g") + ni_model.prepare() + + ni_model.parameters["ni_model.s"].value = 1.0 + + session = RefinementSession() + session.solve( + profiles=[profile], + models=[ni_model], + variables=[ + ni_model.parameters["ni_model.s"], + pdf_model.parameters["pdf.phase.lattice.a"], + ], + )