Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions news/misc_functionalities.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**Added:**

* Add generator parallelism.
* Add soft bounds.
* add a tool to download cif file from database.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
2 changes: 2 additions & 0 deletions requirements/conda.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ diffpy.srreal
diffpy.structure
networkx
mcp[cli]
mp-api
pymatgen
51 changes: 46 additions & 5 deletions src/diffpy/apps/refinebase/parametric_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import networkx as nx
import numpy
from pyobjcryst import loadCrystal

from diffpy.srfit.fitbase import FitContribution
Expand Down Expand Up @@ -245,7 +246,14 @@ def evaluate(self):
class ParametricModelPDF(ParametricModel):
# NOTE: qmin, qmax, stype(scattering type) are meta handled
# throughout the loaded profile in the refinement session
def __init__(self, name, structure, spacegroup_symbol="P1", finite=False):
def __init__(
self,
name,
structure,
spacegroup_symbol="P1",
finite=False,
run_parallel=True,
):
"""
Create a ParametricModelPDF instance from a structure object.

Expand All @@ -269,6 +277,25 @@ def __init__(self, name, structure, spacegroup_symbol="P1", finite=False):
self.space_group_symbol = spacegroup_symbol
self.sgpar_names = []
self._rebuild_graph()
if run_parallel:
try:
import multiprocessing
from multiprocessing import Pool

import psutil
except ImportError:
print(
"\nYou don't appear to have the "
"necessary packages for parallelization"
)
syst_cores = multiprocessing.cpu_count()
cpu_percent = psutil.cpu_percent()
avail_cores = numpy.floor(
(100 - cpu_percent) / (100.0 / syst_cores)
)
ncpu = int(numpy.max([1, avail_cores]))
pool = Pool(processes=ncpu)
self.calc_obj.parallel(ncpu=ncpu, mapfunc=pool.map)

def _hide_dependent_parameters(self, use_uiso=True):
if use_uiso:
Expand Down Expand Up @@ -545,7 +572,11 @@ def residual(self):


def create_pdf_model_from_file(
name, structure_file_path, library="Diffpy", finite=False
name,
structure_file_path,
library="Diffpy",
finite=False,
run_parallel=True,
):
"""Create a ParametricModelPDF by parsing a structure file."""
stru_parser = get_parser("auto")
Expand All @@ -557,16 +588,21 @@ def create_pdf_model_from_file(
) or library == "ObjCryst":
structure = loadCrystal(structure_file_path)
return ParametricModelPDF(
name, structure, spacegroup_symbol=spacegroup_symbol, finite=finite
name,
structure,
spacegroup_symbol=spacegroup_symbol,
finite=finite,
run_parallel=run_parallel,
)


def create_pdf_model_from_model(name, from_model):
def create_pdf_model_from_model(name, from_model, run_parallel=True):
"""Create a ParametricModelPDF sharing the phase of from_model."""
return ParametricModelPDF(
name,
from_model.calc_obj.phase,
spacegroup_symbol=from_model.space_group_symbol,
run_parallel=run_parallel,
)


Expand All @@ -577,6 +613,7 @@ def create_pdf_model_from_code(
global_namespace={},
local_structure_name="structure",
finite=False,
run_parallel=True,
):
"""Create a ParametricModelPDF by executing code that builds a structure.

Expand Down Expand Up @@ -618,5 +655,9 @@ def create_pdf_model_from_code(
)

return ParametricModelPDF(
name, structure, spacegroup_symbol=spacegroup_symbol, finite=finite
name,
structure,
spacegroup_symbol=spacegroup_symbol,
finite=finite,
run_parallel=run_parallel,
)
67 changes: 60 additions & 7 deletions src/diffpy/apps/refinebase/refinement_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ async def add_profile_from_file(
async def add_profile_from_arrays(
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"] = None,
xname: Annotated[str, "Name of the x-axis for the profile"] = "x",
Expand All @@ -92,7 +91,6 @@ async def add_profile_from_arrays(
session.add_profile_from_arrays(
xarray,
yarray,
dx=dx,
dy=dy,
profile_name=profile_name,
xname=xname,
Expand Down Expand Up @@ -182,6 +180,24 @@ async def add_pdf_model(
from_model_name: Annotated[
str, "Name of the existing model to base the new model on"
] = None,
code: Annotated[
str, "Code that builds a structure to base the new model on"
] = None,
library: Annotated[
str, "Structure library to use ('Diffpy' or 'ObjCryst')"
] = "Diffpy",
global_namespace: Annotated[
dict, "Global namespace to execute 'code' in"
] = {},
local_structure_name: Annotated[
str, "Name of the structure variable assigned by 'code'"
] = "structure",
finite: Annotated[
bool, "Whether to use DebyePDFGenerator instead of PDFGenerator"
] = False,
run_parallel: Annotated[
bool, "Whether to run the PDF model in parallel"
] = True,
) -> str:
"""
Add a structure-file-based parametric model to the refinement session.
Expand All @@ -193,18 +209,39 @@ async def add_pdf_model(
structure_file_path : str, optional
Path to the structure file.
from_model_name : str, optional
code : str, optional
Code that builds a structure to base the new model on.
library : str, optional
Structure library to use ('Diffpy' or 'ObjCryst').
global_namespace : dict, optional
Global namespace to execute 'code' in.
local_structure_name : str, optional
Name of the structure variable assigned by 'code'.
finite : bool, optional
Whether to use DebyePDFGenerator instead of PDFGenerator.
run_parallel : bool, optional
Whether to run the PDF model in parallel.

Notes
-----
'from_model_name' is used to create a new model referencing to an
existing computation object. For example, when the same phase's signal
is observed in multiple profiles, 'from_model_name' allows the refinement
backend to modify the same structure across multiple profiles.

Exactly one of 'structure_file_path', 'from_model_name', or 'code'
must be provided.
"""
session.add_pdf_model(
model_name=model_name,
structure_file_path=structure_file_path,
from_model_name=from_model_name,
code=code,
library=library,
global_namespace=global_namespace,
local_structure_name=local_structure_name,
finite=finite,
run_parallel=run_parallel,
)
return f"Model {model_name} added successfully."

Expand Down Expand Up @@ -468,8 +505,8 @@ async def solve(
"and the second dict is variable-constraint_equation pair."
),
] = None,
restraints: Annotated[
list[str], "List of restraints to apply during the refinement"
bounds: Annotated[
dict, "Dictionary of bounds for variables or equations"
] = None,
name: Annotated[str, "Name of the refinement session"] = None,
weights: Annotated[
Expand Down Expand Up @@ -498,8 +535,24 @@ async def solve(
constraints : list[dict], optional
First dict is new_variable-initial value pair,
and the second dict is variable-constraint_equation pair.
restraints : list[str], optional
List of restraints to apply during the refinement.
bounds : dict, optional
Dictionary of bounds for variables or equations.
e.g. {"variable_name":
{
"lower_bound": 0,
"upper_bound": 10,
"uncertainty": 1,
"scaled": False
}}
# start copied from diffpy.srfit docstring
scaled : bool, optional
If True, the restraint penalty is scaled by the unrestrained
point-average chi^2 (chi^2/numpoints) (default is False).
params : dict, optional
The dictionary of Parameters, indexed by name, that are used in
`param_or_eq` (if an equation string is used) but are not part
of the RecipeOrganizer (default is {}).
# end copied from diffpy.srfit docstring
name : str, optional
Name of the refinement session.
weights : list[float], optional
Expand All @@ -526,7 +579,7 @@ async def solve(
variable_names,
residual_equations=residual_equations,
constraints=constraints,
restraints=restraints,
bounds=bounds,
name=name,
weights=weights,
metas=metas,
Expand Down
34 changes: 24 additions & 10 deletions src/diffpy/apps/refinebase/refinement_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def add_profile_from_arrays(
self,
xarray,
yarray,
dx=None,
dy=None,
profile_name: str = None,
xname: str = "x",
Expand All @@ -68,7 +67,7 @@ def add_profile_from_arrays(
if profile_name is None:
profile_name = str(uuid.uuid4())
profile = Profile()
profile.setObservedProfile(xarray, yarray, dx=dx, dy=dy)
profile.setObservedProfile(xobs=xarray, yobs=yarray, dyobs=dy)
profile.xpar.name = xname
profile._xname = xname
profile.ypar.name = yname
Expand Down Expand Up @@ -128,6 +127,7 @@ def add_pdf_model(
global_namespace={},
local_structure_name="structure",
finite=False,
run_parallel=True,
):
from diffpy.apps.refinebase.parametric_model import (
create_pdf_model_from_code,
Expand All @@ -143,6 +143,7 @@ def add_pdf_model(
structure_file_path,
library=library,
finite=finite,
run_parallel=run_parallel,
)
elif from_model_name is not None:
if from_model_name not in self.models_dict:
Expand All @@ -153,6 +154,7 @@ def add_pdf_model(
pdf_model = create_pdf_model_from_model(
model_name,
from_model,
run_parallel=run_parallel,
)
elif code is not None:
pdf_model = create_pdf_model_from_code(
Expand All @@ -161,6 +163,7 @@ def add_pdf_model(
global_namespace=global_namespace,
local_structure_name=local_structure_name,
finite=finite,
run_parallel=run_parallel,
)
else:
raise ValueError(
Expand Down Expand Up @@ -443,19 +446,19 @@ def _solve(
models,
variable_names,
constraints=None,
restraints=None,
bounds=None,
weights=None,
residual_equations=None,
metas=None,
verbose_iterations=0,
):
# NOTE: restraints to be implemented
recipe = FitRecipe()
self.recipes_dict[name] = recipe
if weights is None:
weights = numpy.ones(len(profiles))
if residual_equations is None:
residual_equations = ["chiv"] * len(profiles)
if bounds is None:
bounds = {}
if metas is not None:
for i in range(len(metas)):
profiles[i].meta.update(metas[i])
Expand Down Expand Up @@ -490,7 +493,20 @@ def _solve(
if var in recipe._parameters.values():
continue
recipe.add_variable(var, name=variable_names[i])

for eq_or_var_name, arg_dict in bounds.items():
lb = arg_dict.get("lower_bound", -numpy.inf)
ub = arg_dict.get("upper_bound", numpy.inf)
use_soft_bounds = arg_dict.get("use_soft_bounds", True)
if use_soft_bounds:
uncertainty = arg_dict.get("uncertainty", 1)
scaled = arg_dict.get("scaled", False)
eq_or_var_name = eq_or_var_name.replace(".", "_")
recipe.add_soft_bounds(
eq_or_var_name, lb, ub, sig=uncertainty, scaled=scaled
)
else:
par = self.get_variable(eq_or_var_name)["obj"]
par.bound_range(lb, ub)
recipe.free("all")
leastsq(recipe.residual, recipe.getValues())
# NOTE: non-scalar value will raise error in `get_results_string`
Expand All @@ -507,12 +523,11 @@ def solve(
variable_names=[],
residual_equations=None,
constraints=None,
restraints=None,
bounds=None,
name=uuid.uuid4(),
weights=None,
metas=None,
include_sgpars=False,
verbose_iterations=0,
):
profiles = []
for profile_name in profile_names:
Expand Down Expand Up @@ -551,11 +566,10 @@ def solve(
variable_names=variable_names,
residual_equations=residual_equations,
constraints=constraints,
restraints=restraints,
bounds=bounds,
name=name,
weights=weights,
metas=metas,
verbose_iterations=verbose_iterations,
)

def plot(self):
Expand Down
Loading
Loading