From c2686b4cc40910e52d7eebc001a5b39ad721ff06 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:13:46 -0400 Subject: [PATCH 01/13] chore: copy structure subpackage and tests from diffpy.srfit Copy src/diffpy/srfit/structure/ and its tests (test_diffpyparset, test_objcrystparset, test_sgconstraints, plus LaMnO3.stru) verbatim from diffpy.srfit upstream/main (8d8931a). No edits, so imports still point at diffpy.srfit.structure and are fixed in the next commit. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/__init__.py | 69 +- .../cmistructure/basestructureparset.py | 64 + src/diffpy/cmistructure/bvsrestraint.py | 114 + src/diffpy/cmistructure/cctbxparset.py | 325 +++ src/diffpy/cmistructure/diffpyparset.py | 344 +++ src/diffpy/cmistructure/objcrystparset.py | 1951 +++++++++++++++++ src/diffpy/cmistructure/sgconstraints.py | 867 ++++++++ src/diffpy/cmistructure/srrealparset.py | 98 + tests/test_diffpyparset.py | 133 ++ tests/test_objcrystparset.py | 685 ++++++ tests/test_sgconstraints.py | 288 +++ tests/testdata/LaMnO3.stru | 129 ++ 12 files changed, 5055 insertions(+), 12 deletions(-) create mode 100644 src/diffpy/cmistructure/basestructureparset.py create mode 100644 src/diffpy/cmistructure/bvsrestraint.py create mode 100644 src/diffpy/cmistructure/cctbxparset.py create mode 100644 src/diffpy/cmistructure/diffpyparset.py create mode 100644 src/diffpy/cmistructure/objcrystparset.py create mode 100644 src/diffpy/cmistructure/sgconstraints.py create mode 100644 src/diffpy/cmistructure/srrealparset.py create mode 100644 tests/test_diffpyparset.py create mode 100644 tests/test_objcrystparset.py create mode 100644 tests/test_sgconstraints.py create mode 100644 tests/testdata/LaMnO3.stru diff --git a/src/diffpy/cmistructure/__init__.py b/src/diffpy/cmistructure/__init__.py index 77affe7..0990c76 100644 --- a/src/diffpy/cmistructure/__init__.py +++ b/src/diffpy/cmistructure/__init__.py @@ -1,23 +1,68 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2026 Contributors to diffpy.cmistructure. -# All rights reserved. +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. # -# File coded by: Members of the diffpy community. +# File coded by: Chris Farrow # -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors -# -# See LICENSE.rst for license information. +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. # ############################################################################## -"""diffpy.cmi package for doing refinements with structure objects""" +"""Modules and classes that adapt structure representations to the +ParameterSet interface and automatic structure constraint generation +from space group information.""" + +from diffpy.srfit.structure.sgconstraints import ( + constrain_as_space_group, + constrainAsSpaceGroup, +) + + +def struToParameterSet(name, stru): + """Creates a ParameterSet from an structure. + + This returns a ParameterSet adapted for the structure depending on its + type. + + Parameters + ---------- + stru + a structure object known by this module + name + A name to give the structure. + + Raises TypeError if stru cannot be adapted + """ + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + + if DiffpyStructureParSet.canAdapt(stru): + return DiffpyStructureParSet(name, stru) + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + + if ObjCrystCrystalParSet.canAdapt(stru): + return ObjCrystCrystalParSet(name, stru) + + from diffpy.srfit.structure.objcrystparset import ObjCrystMoleculeParSet + + if ObjCrystMoleculeParSet.canAdapt(stru): + return ObjCrystMoleculeParSet(name, stru) + + from diffpy.srfit.structure.cctbxparset import CCTBXCrystalParSet + + if CCTBXCrystalParSet.canAdapt(stru): + return CCTBXCrystalParSet(name, stru) + + raise TypeError("Unadaptable structure format") + -# package version -from diffpy.cmistructure.version import __version__ # noqa +# silence pyflakes checker +assert constrain_as_space_group +assert constrainAsSpaceGroup -# silence the pyflakes syntax checker -assert __version__ or True # End of file diff --git a/src/diffpy/cmistructure/basestructureparset.py b/src/diffpy/cmistructure/basestructureparset.py new file mode 100644 index 0000000..4c97577 --- /dev/null +++ b/src/diffpy/cmistructure/basestructureparset.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Base class for adapting structures to a ParameterSet interface. + +The BaseStructureParSet is a ParameterSet with functionality required by +all structure adapters. +""" + +__all__ = ["BaseStructureParSet"] + +from diffpy.srfit.fitbase.parameterset import ParameterSet + + +class BaseStructureParSet(ParameterSet): + """Base class for structure adapters. + + BaseStructureParSet derives from ParameterSet and provides methods that + help interface the ParameterSet with the space group constraint methods in + the sgconstraints module and to ProfileGenerators. + + Attributes + ---------- + stru + The adapted object + """ + + @classmethod + def canAdapt(self, stru): + """Return whether the structure can be adapted by this class.""" + return False + + def getLattice(self): + """Get a ParameterSet containing the lattice Parameters. + + The returned ParameterSet may contain other Parameters than the + lattice Parameters. It is assumed that the lattice parameters + are named "a", "b", "c", "alpha", "beta", "gamma". + + Lattice must also have the "angunits" attribute, which is either + "deg" or "rad", to signify degrees or radians. + """ + raise NotImplementedError("The must be overloaded") + + def getScatterers(self): + """Get a list of ParameterSets that represents the scatterers. + + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. + """ + raise NotImplementedError("The must be overloaded") diff --git a/src/diffpy/cmistructure/bvsrestraint.py b/src/diffpy/cmistructure/bvsrestraint.py new file mode 100644 index 0000000..ec15644 --- /dev/null +++ b/src/diffpy/cmistructure/bvsrestraint.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Bond-valence sum calculator from SrReal wrapped as a Restraint. + +This can be used as an addition to a cost function during a structure +refinement to keep the bond-valence sum within tolerable limits. +""" + +__all__ = ["BVSRestraint"] + +from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.restraint import Restraint + + +class BVSRestraint(Restraint): + """Wrapping of BVSCalculator.bvmsdiff as a Restraint. + + The restraint penalty is the root-mean-square deviation of the theoretical + and calculated bond-valence sum of a structure. + + Attributes + ---------- + _calc + The SrReal BVSCalculator instance. + _parset + The SrRealParSet that created this BVSRestraint. + sig + The uncertainty on the BVS (default 1). + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + """ + + def __init__(self, parset, sig=1, scaled=False): + """Initialize the Restraint. + + Parameters + ---------- + parset + SrRealParSet that creates this BVSRestraint. + sig + The uncertainty on the BVS (default 1). + scaled + A flag indicating if the restraint is scaled + (multiplied) by the unrestrained point-average chi^2 + (chi^2/numpoints) (bool, default False). + """ + from diffpy.srreal.bvscalculator import BVSCalculator + + self._calc = BVSCalculator() + self._parset = parset + self.sig = float(sig) + self.scaled = bool(scaled) + return + + def penalty(self, w=1.0): + """Calculate the penalty of the restraint. + + Parameters + ---------- + w + The point-average chi^2 which is optionally used to scale the + penalty (float, default 1.0). + """ + # Get the bvms from the BVSCalculator + stru = self._parset._get_srreal_structure() + self._calc.eval(stru) + penalty = self._calc.bvmsdiff + + # Scale by the prefactor + penalty /= self.sig**2 + + # Optionally scale by w + if self.scaled: + penalty *= w + + return penalty + + def _validate(self): + """This evaluates the calculator. + + Raises SrFitError if validation fails. + """ + from numpy import nan + + p = self.penalty() + if p is None or p is nan: + raise SrFitError("Cannot evaluate penalty") + v = self._calc.value + if len(v) > 1 and not v.any(): + emsg = ( + "Bond valence sums are all zero. Check atom symbols in " + "the structure or define custom bond-valence parameters." + ) + raise SrFitError(emsg) + return + + # End of class BVSRestraint + + +# End of file diff --git a/src/diffpy/cmistructure/cctbxparset.py b/src/diffpy/cmistructure/cctbxparset.py new file mode 100644 index 0000000..d2c8902 --- /dev/null +++ b/src/diffpy/cmistructure/cctbxparset.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Wrappers for interfacing cctbx crystal with SrFit. + +This wraps a cctbx.crystal as a ParameterSet with a similar hierarchy, which +can then be used within a FitRecipe. Note that all manipulations to the +cctbx.crystal should be done before wrapping. Changes made to the cctbx.crystal +object after wrapping may not be reflected within the wrapper, which can have +unpredictable results during a structure refinement. + +The following classes are adapted: + +- `CCTBXCrystalParSet`: wrapper for `cctbx.crystal`. +- `CCTBXUnitCellParSet`: wrapper for the unit cell of `cctbx.crystal`. +- `CCTBXScattererParSet`: wrapper for `cctbx.xray.scatterer`. +""" + +from diffpy.srfit.fitbase.parameter import ParameterAdapter +from diffpy.srfit.fitbase.parameterset import ParameterSet +from diffpy.srfit.structure.basestructureparset import BaseStructureParSet + +__all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", "CCTBXCrystalParSet"] + + +class CCTBXScattererParSet(ParameterSet): + """A wrapper for cctbx.xray.scatterer. + + This class derives from ParameterSet. + + Attributes + ---------- + name + Name of the scatterer. The name is always of the form + "%s%i" % (element, number), where the number is the running + index of that element type (starting at 0). + x (y, z) -- Atom position in crystal coordinates (ParameterAdapter) + occupancy + Occupancy of the atom on its crystal location + (ParameterAdapter) + Uiso + Isotropic scattering factor (ParameterAdapter). + """ + + def __init__(self, name, strups, idx): + """Initialize. + + Parameters + ---------- + name + The name of this scatterer. + strups + The CCTBXCrystalParSet that contains the cctbx structure + idx + The index of the scatterer in the structure. + """ + ParameterSet.__init__(self, name) + self.strups = strups + self.idx = idx + + # x, y, z, occupancy + self.add_parameter( + ParameterAdapter("x", None, self._xyzgetter(0), self._xyzsetter(0)) + ) + self.add_parameter( + ParameterAdapter("y", None, self._xyzgetter(1), self._xyzsetter(1)) + ) + self.add_parameter( + ParameterAdapter("z", None, self._xyzgetter(2), self._xyzsetter(2)) + ) + self.add_parameter( + ParameterAdapter("occupancy", None, self._getocc, self._setocc) + ) + self.add_parameter( + ParameterAdapter("Uiso", None, self._getuiso, self._setuiso) + ) + return + + # Getters and setters + + def _xyzgetter(self, i): + + def f(dummy): + return self.strups.stru.scatterers()[self.idx].site[i] + + return f + + def _xyzsetter(self, i): + + def f(dummy, value): + xyz = list(self.strups.stru.scatterers()[self.idx].site) + xyz[i] = value + self.strups.stru.scatterers()[self.idx].site = tuple(xyz) + return + + return f + + def _getocc(self, dummy): + return self.strups.stru.scatterers()[self.idx].occupancy + + def _setocc(self, dummy, value): + self.strups.stru.scatterers()[self.idx].occupancy = value + return + + def _getuiso(self, dummy): + return self.strups.stru.scatterers()[self.idx].u_iso + + def _setuiso(self, dummy, value): + self.strups.stru.scatterers()[self.idx].u_iso = value + return + + def _getelem(self): + return self.stru.element_symbol() + + element = property(_getelem) + + +# End class CCTBXScattererParSet + + +class CCTBXUnitCellParSet(ParameterSet): + """A wrapper for cctbx unit_cell object. + + Attributes + ---------- + name + Always "unitcell". + a + Unit cell parameters (ParameterAdapter). + b + Unit cell parameters (ParameterAdapter). + c + Unit cell parameters (ParameterAdapter). + alpha + Unit cell parameters (ParameterAdapter). + beta + Unit cell parameters (ParameterAdapter). + gamma + Unit cell parameters (ParameterAdapter). + """ + + def __init__(self, strups): + """Initialize. + + Parameters + ---------- + strups + The CCTBXCrystalParSet that contains the cctbx structure + and the unit cell we're wrapper. + """ + ParameterSet.__init__(self, "unitcell") + self.strups = strups + self._latpars = list(self.strups.stru.unit_cell().parameters()) + + self.add_parameter( + ParameterAdapter("a", None, self._latgetter(0), self._latsetter(0)) + ) + self.add_parameter( + ParameterAdapter("b", None, self._latgetter(1), self._latsetter(1)) + ) + self.add_parameter( + ParameterAdapter("c", None, self._latgetter(2), self._latsetter(2)) + ) + self.add_parameter( + ParameterAdapter( + "alpha", None, self._latgetter(3), self._latsetter(3) + ) + ) + self.add_parameter( + ParameterAdapter( + "beta", None, self._latgetter(4), self._latsetter(4) + ) + ) + self.add_parameter( + ParameterAdapter( + "gamma", None, self._latgetter(5), self._latsetter(5) + ) + ) + + return + + def _latgetter(self, i): + + def f(dummy): + return self._latpars[i] + + return f + + def _latsetter(self, i): + + def f(dummy, value): + self._latpars[i] = value + self.strups._update = True + return + + return f + + +# End class CCTBXUnitCellParSet + +# FIXME - Special positions should be constant. + + +class CCTBXCrystalParSet(BaseStructureParSet): + """A wrapper for CCTBX structure. + + Attributes + ---------- + stru + The adapted cctbx structure object. + scatterers + The list of ScattererParSets. + unitcell + The CCTBXUnitCellParSet for the structure. + """ + + def __init__(self, name, stru): + """Initialize. + + Parameters + ---------- + name + A name for this + stru + A CCTBX structure instance. + """ + ParameterSet.__init__(self, name) + self.stru = stru + self.add_parameter_set(CCTBXUnitCellParSet(self)) + self.scatterers = [] + + self._update = False + + cdict = {} + for s in stru.scatterers(): + el = s.element_symbol() + i = cdict.get(el, 0) + sname = "%s%i" % (el, i) + cdict[el] = i + 1 + scatterer = CCTBXScattererParSet(sname, self, i) + self.add_parameter_set(scatterer) + self.scatterers.append(scatterer) + + # Constrain the lattice + from diffpy.srfit.structure.sgconstraints import _constrain_space_group + + symbol = self.getSpaceGroup() + _constrain_space_group(self, symbol) + + return + + def update(self): + """Update the unit_cell to a change in lattice parameters. + + This remakes the unit cell according to a change in the lattice + parameters. Call this function before using the + CCTBXCrystalParSet. The unit_cell will only be remade if + necessary. + """ + if not self._update: + return + + self._update = False + stru = self.stru + sgn = stru.space_group().match_tabulated_settings().number() + + # Create the symmetry object + from cctbx.crystal import symmetry + + symm = symmetry( + unit_cell=self.unitcell._latpars, space_group_symbol=sgn + ) + + # Now the new structure + newstru = stru.__class__( + crystal_symmetry=symm, scatterers=stru.scatterers() + ) + + self.unitcell._latpars = list(newstru.unit_cell().parameters()) + + self.stru = newstru + return + + @classmethod + def canAdapt(self, stru): + """Return whether the structure can be adapted by this class.""" + try: + from cctbx.crystal import special_position_settings + except ImportError: + return False + return isinstance(stru, special_position_settings) + + def getLattice(self): + """Get the ParameterSet containing the lattice Parameters.""" + return self.unitcell + + def getScatterers(self): + """Get a list of ParameterSets that represents the scatterers. + + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. + """ + return self.scatterers + + def getSpaceGroup(self): + """Get the HM space group symbol for the structure.""" + sg = self.stru.space_group() + t = sg.type() + return t.lookup_symbol() + + +# End class CCTBXCrystalParSet diff --git a/src/diffpy/cmistructure/diffpyparset.py b/src/diffpy/cmistructure/diffpyparset.py new file mode 100644 index 0000000..6d2d17b --- /dev/null +++ b/src/diffpy/cmistructure/diffpyparset.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Adapters for interfacing a diffpy.structure.Structure with SrFit. + +A diffpy.structure.Structure object is meant to be passed to a +DiffpyStructureParSet object from this module, which can then be used as a +ParameterSet. (It has other methods for interfacing with SrReal calculator +adapters.) Any change to the lattice or existing atoms will be registered with +the Structure. Changes in the number of atoms will not be recognized. Thus, +the diffpy.structure.Structure object should be fully configured before passing +it to DiffpyStructureParSet. + +The following classes are adapted: + +- `DiffpyStructureParSet`: adapter for `diffpy.structure.Structure`. +- `DiffpyLatticeParSet`: adapter for `diffpy.structure.Lattice`. +- `DiffpyAtomParSet`: adapter for `diffpy.structure.Atom`. +""" + +__all__ = ["DiffpyStructureParSet"] + +from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy +from diffpy.srfit.fitbase.parameterset import ParameterSet +from diffpy.srfit.structure.srrealparset import SrRealParSet +from diffpy.srfit.util.argbinders import bind2nd + + +# Accessor for xyz of atoms +class _xyzgetter(object): + + def __init__(self, i): + self.i = i + + def __call__(self, atom): + return atom.xyz[self.i] + + +class _xyzsetter(object): + + def __init__(self, i): + self.i = i + + def __call__(self, atom, value): + atom.xyz[self.i] = value + + +class DiffpyAtomParSet(ParameterSet): + """A wrapper for diffpy.structure.Atom. + + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. See + this class for base attributes. + + Attributes + ---------- + atom + The diffpy.structure.Atom this is adapting + element + The element name (property). + + Managed Parameters + ------------------ + occupancy + Occupancy of the atom on its crystal location + (ParameterAdapter) + occ + Proxy for occupancy (ParameterProxy). + U11, U22, U33, U12, U21, U23, U32, U13, U31 + -- Anisotropic displacement factor for atom (ParameterAdapter + or ParameterProxy). Note that the Uij and Uji parameters + are the same. + Uiso + Isotropic ADP (ParameterAdapter). + B11, B22, B33, B12, B21, B23, B32, B13, B31 + -- Anisotropic displacement factor for atom (ParameterAdapter + or ParameterProxy). Note that the Bij and Bji parameters + are the same. (Bij = 8*pi**2*Uij) + Biso + Isotropic ADP (ParameterAdapter). + """ + + def __init__(self, name, atom): + """Initialize. + + Parameters + ---------- + atom + A diffpy.structure.Atom instance + """ + ParameterSet.__init__(self, name) + self.atom = atom + a = atom + # x, y, z, occupancy + self.add_parameter( + ParameterAdapter("x", a, _xyzgetter(0), _xyzsetter(0)) + ) + self.add_parameter( + ParameterAdapter("y", a, _xyzgetter(1), _xyzsetter(1)) + ) + self.add_parameter( + ParameterAdapter("z", a, _xyzgetter(2), _xyzsetter(2)) + ) + occupancy = ParameterAdapter("occupancy", a, attr="occupancy") + self.add_parameter(occupancy) + self.add_parameter(ParameterProxy("occ", occupancy)) + # U + self.add_parameter(ParameterAdapter("U11", a, attr="U11")) + self.add_parameter(ParameterAdapter("U22", a, attr="U22")) + self.add_parameter(ParameterAdapter("U33", a, attr="U33")) + U12 = ParameterAdapter("U12", a, attr="U12") + U21 = ParameterProxy("U21", U12) + U13 = ParameterAdapter("U13", a, attr="U13") + U31 = ParameterProxy("U31", U13) + U23 = ParameterAdapter("U23", a, attr="U23") + U32 = ParameterProxy("U32", U23) + self.add_parameter(U12) + self.add_parameter(U21) + self.add_parameter(U13) + self.add_parameter(U31) + self.add_parameter(U23) + self.add_parameter(U32) + self.add_parameter(ParameterAdapter("Uiso", a, attr="Uisoequiv")) + # B + self.add_parameter(ParameterAdapter("B11", a, attr="B11")) + self.add_parameter(ParameterAdapter("B22", a, attr="B22")) + self.add_parameter(ParameterAdapter("B33", a, attr="B33")) + B12 = ParameterAdapter("B12", a, attr="B12") + B21 = ParameterProxy("B21", B12) + B13 = ParameterAdapter("B13", a, attr="B13") + B31 = ParameterProxy("B31", B13) + B23 = ParameterAdapter("B23", a, attr="B23") + B32 = ParameterProxy("B32", B23) + self.add_parameter(B12) + self.add_parameter(B21) + self.add_parameter(B13) + self.add_parameter(B31) + self.add_parameter(B23) + self.add_parameter(B32) + self.add_parameter(ParameterAdapter("Biso", a, attr="Bisoequiv")) + return + + def __repr__(self): + return repr(self.atom) + + def _getelem(self): + return self.atom.element + + def _setelem(self, el): + self.atom.element = el + + element = property(_getelem, _setelem, "type of atom") + + +# End class DiffpyAtomParSet + + +def _latgetter(par): + return bind2nd(getattr, par) + + +def _latsetter(par): + return bind2nd(setattr, par) + + +class DiffpyLatticeParSet(ParameterSet): + """A wrapper for diffpy.structure.Lattice. + + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. + See this class for base attributes. + + Attributes + ---------- + lattice + The diffpy.structure.Lattice this is adapting + name + Always "lattice" + angunits + "deg", the units of angle + + Parameters + ---------- + a + Unit cell parameters (ParameterAdapter). + b + Unit cell parameters (ParameterAdapter). + c + Unit cell parameters (ParameterAdapter). + alpha + Unit cell parameters (ParameterAdapter). + beta + Unit cell parameters (ParameterAdapter). + gamma + Unit cell parameters (ParameterAdapter). + """ + + def __init__(self, lattice): + """Initialize. + + Parameters + ---------- + lattice + A diffpy.structure.Lattice instance + """ + ParameterSet.__init__(self, "lattice") + self.angunits = "deg" + self.lattice = lattice + lat = lattice + self.add_parameter( + ParameterAdapter("a", lat, _latgetter("a"), _latsetter("a")) + ) + self.add_parameter( + ParameterAdapter("b", lat, _latgetter("b"), _latsetter("b")) + ) + self.add_parameter( + ParameterAdapter("c", lat, _latgetter("c"), _latsetter("c")) + ) + self.add_parameter( + ParameterAdapter( + "alpha", lat, _latgetter("alpha"), _latsetter("alpha") + ) + ) + self.add_parameter( + ParameterAdapter( + "beta", lat, _latgetter("beta"), _latsetter("beta") + ) + ) + self.add_parameter( + ParameterAdapter( + "gamma", lat, _latgetter("gamma"), _latsetter("gamma") + ) + ) + return + + def __repr__(self): + return repr(self.lattice) + + +# End class DiffpyLatticeParSet + + +class DiffpyStructureParSet(SrRealParSet): + """A wrapper for diffpy.structure.Structure. + + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. See + this class for base attributes. + + Attributes + ---------- + atoms + The list of DiffpyAtomParSets, provided for convenience. + stru + The diffpy.structure.Structure this is adapting + + Managed ParameterSets + --------------------- + lattice + The managed DiffpyLatticeParSet + + A managed DiffpyAtomParSets. is the atomic element and + is the index of that element in the structure, + starting from zero. Thus, for nickel in P1 symmetry, the + managed DiffpyAtomParSets will be named "Ni0", "Ni1", "Ni2" + and "Ni3". + """ + + def __init__(self, name, stru): + """Initialize. + + Parameters + ---------- + name + A name for the structure + stru + A diffpy.structure.Structure instance + """ + SrRealParSet.__init__(self, name) + self.stru = stru + self.add_parameter_set(DiffpyLatticeParSet(stru.lattice)) + self.atoms = [] + + cdict = {} + for a in stru: + el = a.element.title() + # Try to sanitize the name. + el = el.replace("+", "p") + el = el.replace("-", "m") + i = cdict.get(el, 0) + aname = "%s%i" % (el, i) + cdict[el] = i + 1 + atom = DiffpyAtomParSet(aname, a) + self.add_parameter_set(atom) + self.atoms.append(atom) + + return + + def __repr__(self): + return repr(self.stru) + + def getLattice(self): + """Get the ParameterSet containing the lattice Parameters.""" + return self.lattice + + @classmethod + def canAdapt(self, stru): + """Return whether the structure can be adapted by this class.""" + from diffpy.structure import Structure + + return isinstance(stru, Structure) + + def getScatterers(self): + """Get a list of ParameterSets that represents the scatterers. + + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. + """ + return self.atoms + + def _get_srreal_structure(self): + """Get the structure object for use with SrReal calculators. + + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. This takes the extra step of + wrapping the structure in a nometa wrapper. + """ + from diffpy.srreal.structureadapter import nometa + + stru = SrRealParSet._get_srreal_structure(self) + return nometa(stru) + + +# End class DiffpyStructureParSet diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py new file mode 100644 index 0000000..3ccf993 --- /dev/null +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -0,0 +1,1951 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Wrappers for adapting pyobjcryst.crystal.Crystal to a srfit +ParameterSet. + +This will adapt a Crystal or Molecule object from pyobjcryst into the +ParameterSet interface. The following classes are adapted: + +- `ObjCrystCrystalParSet`: adapter for `pyobjcryst.crystal.Crystal`. +- `ObjCrystAtomParSet`: adapter for `pyobjcryst.atom.Atom`. +- `ObjCrystMoleculeParSet`: adapter for `pyobjcryst.molecule.Molecule`. +- `ObjCrystMolAtomParSet`: adapter for `pyobjcryst.molecule.MolAtom`. + +Related to the adaptation of Molecule and MolAtom, there are adaptors +for specifying molecule restraints: + +- `ObjCrystBondLengthRestraint` +- `ObjCrystBondAngleRestraint` +- `ObjCrystDihedralAngleRestraint` + +There are also Parameters for encapsulating and modifying atoms via +their relative positions. These Parameters can also act like +constraints, and can modify the positions of multiple MolAtoms: + +- `ObjCrystBondLengthParameter` +- `ObjCrystBondAngleParameter` +- `ObjCrystDihedralAngleParameter` +""" + +__all__ = ["ObjCrystMoleculeParSet", "ObjCrystCrystalParSet"] + +import numpy +from pyobjcryst.molecule import ( + GetBondAngle, + GetBondLength, + GetDihedralAngle, + StretchModeBondAngle, + StretchModeBondLength, + StretchModeTorsion, +) + +from diffpy.srfit.fitbase.parameter import ( + Parameter, + ParameterAdapter, + ParameterProxy, +) +from diffpy.srfit.fitbase.parameterset import ParameterSet +from diffpy.srfit.structure.srrealparset import SrRealParSet +from diffpy.utils._deprecator import build_deprecation_message, deprecated + +removal_version = "4.0.0" +bl_base = "diffpy.srfit.structure.objcrystparset.ObjCrystBondLengthParameter" + +bl_setConst_dep_msg = build_deprecation_message( + bl_base, + "setConst", + "set_constant", + removal_version, +) + +ba_base = "diffpy.srfit.structure.objcrystparset.ObjCrystBondAngleParameter" + +ba_setConst_dep_msg = build_deprecation_message( + ba_base, + "setConst", + "set_constant", + removal_version, +) + +da_base = ( + "diffpy.srfit.structure.objcrystparset.ObjCrystDihedralAngleParameter" +) + +da_setConst_dep_msg = build_deprecation_message( + da_base, + "setConst", + "set_constant", + removal_version, +) + + +class ObjCrystScattererParSet(ParameterSet): + """A base adaptor for an Objcryst Scatterer. + + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet and + adapts pyobjcryst.scatterer.Scatterer derivatives (Molecule, Atom) and + objects with a similar interface (MolAtom). See the ParameterSet class for + base attributes. + + Attributes + ---------- + scat + The adapted pyobjcryst object. + parent + The ParameterSet this belongs to + + Managed Parameters + ------------------ + occ + Occupancy of the scatterer on its crystal site + (ParameterWraper) + """ + + def __init__(self, name, scat, parent): + """Initialize. + + Parameters + ---------- + name + The name of the scatterer + scat + The pyobjcryst.Scatterer instance + parent + The ParameterSet this belongs to + """ + ParameterSet.__init__(self, name) + self.scat = scat + self.parent = parent + + # x, y, z, occ + self.add_parameter(ParameterAdapter("x", self.scat, attr="X")) + self.add_parameter(ParameterAdapter("y", self.scat, attr="Y")) + self.add_parameter(ParameterAdapter("z", self.scat, attr="Z")) + self.add_parameter( + ParameterAdapter("occ", self.scat, attr="Occupancy") + ) + return + + def isDummy(self): + """Indicate whether this scatterer is a dummy atom.""" + return False + + def hasScatterers(self): + """Indicate if this scatterer has its own scatterers.""" + return hasattr(self, "getScatterers") + + +# End class ObjCrystScattererParSet + + +class ObjCrystAtomParSet(ObjCrystScattererParSet): + """A adaptor for a pyobjcryst.Atom. + + This class derives from ObjCrystScattererParSet. + + Attributes + ---------- + scat + The adapted pyobjcryst.atom.Atom. + element + Non-refinable name of the element (property). + parent + The ObjCrystCrystalParSet this belongs to. + + Managed Parameters + ------------------ + occ + Occupancy of the atom on its crystal location + (ParameterAdapter) + Biso + Isotropic scattering factor (ParameterAdapter). + B11, B22, B33, B12, B21, B23, B32, B13, B31 + -- Anisotropic displacement factor for scatterer + (ParameterAdapter or ParameterProxy). Note that the Bij and Bji + parameters are the same. + """ + + def __init__(self, name, atom, parent): + """Initialize. + + Parameters + ---------- + name + The name of the scatterer + scat + The Scatterer instance + parent + The ObjCrystCrystalParSet this belongs to + """ + ObjCrystScattererParSet.__init__(self, name, atom, parent) + sp = atom.GetScatteringPower() + + # The B-parameters + self.add_parameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.add_parameter(ParameterAdapter("B11", sp, attr="B11")) + self.add_parameter(ParameterAdapter("B22", sp, attr="B22")) + self.add_parameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") + B21 = ParameterProxy("B21", B12) + B13 = ParameterAdapter("B13", sp, attr="B13") + B31 = ParameterProxy("B31", B13) + B23 = ParameterAdapter("B23", sp, attr="B23") + B32 = ParameterProxy("B32", B23) + self.add_parameter(B12) + self.add_parameter(B21) + self.add_parameter(B13) + self.add_parameter(B31) + self.add_parameter(B23) + self.add_parameter(B32) + + # Give a value to Biso if it doesn't have one, and this is isotropic + if sp.IsIsotropic() and self.Biso.value == 0: + self.Biso.value = 0.5 + return + + def _getelem(self): + """Getter for the element type.""" + return self.scat.GetScatteringPower().GetSymbol() + + element = property(_getelem) + + +# End class ObjCrystAtomParSet + + +class ObjCrystMoleculeParSet(ObjCrystScattererParSet): + """A adaptor for a pyobjcryst.Molecule. + + This class derives from ObjCrystScattererParSet. + + Attributes + ---------- + scat + The adapted pyobjcryst.molecule.Molecule. + stru + The adapted pyobjcryst.molecule.Molecule. + parent + The ObjCrystCrystalParSet this belongs to. + ObjCrystMoleculeParSets can be used on their own, in which + case this is None. + + Managed Parameters + ------------------ + occ + Occupancy of the molecule on its crystal location + (ParameterAdapter) + q0, q1, q2, q3 -- Orientational quaternion (ParameterAdapter) + + + Other attributes are inherited from + diffpy.srfit.fitbase.parameterset.ParameterSet + """ + + def __init__(self, name, molecule, parent=None): + """Initialize. + + Parameters + ---------- + name + The name of the scatterer + molecule + The pyobjcryst.Molecule instance + parent + The ObjCrystCrystalParSet this belongs to (default None). + """ + ObjCrystScattererParSet.__init__(self, name, molecule, parent) + self.stru = molecule + + # Add orientation quaternion + self.add_parameter(ParameterAdapter("q0", self.scat, attr="Q0")) + self.add_parameter(ParameterAdapter("q1", self.scat, attr="Q1")) + self.add_parameter(ParameterAdapter("q2", self.scat, attr="Q2")) + self.add_parameter(ParameterAdapter("q3", self.scat, attr="Q3")) + + # Wrap the MolAtoms within the molecule + self.atoms = [] + anames = [] + + for a in molecule: + + name = a.GetName() + if not name: + raise AttributeError("Each MolAtom must have a name") + if name in anames: + raise AttributeError("MolAtom name '%s' is duplicated" % name) + + atom = ObjCrystMolAtomParSet(name, a, self) + atom.molecule = self + self.add_parameter_set(atom) + self.atoms.append(atom) + anames.append(name) + + return + + @classmethod + def canAdapt(self, stru): + """Return whether the structure can be adapted by this class.""" + from pyobjcryst.molecule import Molecule + + return isinstance(stru, Molecule) + + # Part of SrRealParSet interface + def useSymmetry(self, use=True): + """Set this structure to use symmetry. + + This structure object does not support symmetry. + """ + return + + # Part of SrRealParSet interface + def usingSymmetry(self): + """Check if symmetry is being used. + + This structure object does not support symmetry. + """ + return False + + # Part of SrRealParSet interface + def _get_srreal_structure(self): + """Get the structure object for use with SrReal calculators. + + Molecule objects are never periodic. Return the object and let + the SrReal adapters do the proper thing. + """ + return self.stru + + def getLattice(self): + """Get the ParameterSet containing the lattice Parameters.""" + lattice = ParameterSet("lattice") + lattice.newPar("a", 1.0) + lattice.newPar("b", 1.0) + lattice.newPar("c", 1.0) + lattice.newPar("alpha", 90) + lattice.newPar("beta", 90) + lattice.newPar("gamma", 90) + lattice.angunits = "deg" + return lattice + + def getScatterers(self): + """Get a list of ParameterSets that represents the scatterers. + + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. + """ + return self.atoms + + def wrapRestraints(self): + """Wrap the restraints implicit to the molecule. + + This will wrap MolBonds, MolBondAngles and MolDihedralAngles of + the Molecule as ObjCrystMoleculeRestraint objects. + """ + # Wrap restraints. Restraints wrapped in this way cannot be modified + # from within this class. + for b in self.scat.GetBondList(): + res = ObjCrystMoleculeRestraint(b) + self._restraints.add(res) + + for ba in self.scat.GetBondAngleList(): + res = ObjCrystMoleculeRestraint(ba) + self._restraints.add(res) + + for da in self.scat.GetDihedralAngleList(): + res = ObjCrystMoleculeRestraint(da) + self._restraints.add(res) + + return + + def wrapStretchModeParameters(self): + """Wrap the stretch modes implicit to the Molecule as + Parameters. + + This will wrap StretchModeBondLengths and StretchModeBondAngles of the + Molecule as Parameters. Note that this requires that the MolBondAtoms + in the Molecule came in with unique names. Torsion angles are not + wrapped, as there is not enough information to determine each MolAtom + in the angle. + + The Parameters will be given the concatenated name of its constituents. + bond lengths: "bl_aname1_aname2" + bond angles: "ba_aname1_aname2_aname3" + """ + for mode in self.scat.GetStretchModeBondLengthList(): + name1 = mode.mpAtom0.GetName() + name2 = mode.mpAtom1.GetName() + + name = "bl_" + "_".join((name1, name2)) + + atom1 = getattr(self, name1) + atom2 = getattr(self, name2) + + par = ObjCrystBondLengthParameter(name, atom1, atom2, mode=mode) + + atoms = [] + for a in mode.GetAtoms(): + name = a.GetName() + atoms.append(getattr(self, name)) + + par.AddAtoms(atoms) + + self.add_parameter(par) + + for mode in self.scat.GetStretchModeBondAngleList(): + name1 = mode.mpAtom0.GetName() + name2 = mode.mpAtom1.GetName() + name3 = mode.mpAtom2.GetName() + + name = "ba_" + "_".join((name1, name2, name3)) + + atom1 = getattr(self, name1) + atom2 = getattr(self, name2) + atom3 = getattr(self, name3) + + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, mode=mode + ) + + atoms = [] + for a in mode.GetAtoms(): + name = a.GetName() + atoms.append(getattr(self, name)) + par.AddAtoms(atoms) + + self.add_parameter(par) + + return + + def restrainBondLength( + self, atom1, atom2, length, sigma, delta, scaled=False + ): + """Add a bond length restraint. + + This creates an instance of ObjCrystBondLengthRestraint and adds it to + the ObjCrystMoleculeParSet. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the bond + atom2 + Second atom (ObjCrystMolAtomParSet) in the bond + length + The length of the bond (Angstroms) + sigma + The uncertainty of the bond length (Angstroms) + delta + The width of the bond (Angstroms) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False) + + Returns + ------- + res + The ObjCrystBondLengthRestraint object for use with the + 'unrestrain' method. + """ + res = ObjCrystBondLengthRestraint( + atom1, atom2, length, sigma, delta, scaled + ) + self._restraints.add(res) + + return res + + def restrainBondLengthParameter( + self, par, length, sigma, delta, scaled=False + ): + """Add a bond length restraint. + + This creates an instance of ObjCrystBondLengthRestraint and adds it to + the ObjCrystMoleculeParSet. + + Parameters + ---------- + par + A ObjCrystBondLengthParameter (see addBondLengthParameter) + length + The length of the bond (Angstroms) + sigma + The uncertainty of the bond length (Angstroms) + delta + The width of the bond (Angstroms) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False) + + Returns + ------- + ObjCrystBondLengthRestraint object + Returns the ObjCrystBondLengthRestraint object for use with the + 'unrestrain' method. + """ + return self.restrainBondLength( + par.atom1, par.atom2, length, sigma, delta, scaled + ) + + def restrainBondAngle( + self, atom1, atom2, atom3, angle, sigma, delta, scaled=False + ): + """Add a bond angle restraint. + + This creates an instance of ObjCrystBondAngleRestraint and adds it to + the ObjCrystMoleculeParSet. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the bond angle + atom2 + Second (central) atom (ObjCrystMolAtomParSet) in the bond + angle + atom3 + Third atom (ObjCrystMolAtomParSet) in the bond angle + angle + The bond angle (radians) + sigma + The uncertainty of the bond angle (radians) + delta + The width of the bond angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + + Returns + ------- + ObjCrystBondAngleRestraint object + Returns the ObjCrystBondAngleRestraint object for use with the + 'unrestrain' method. + """ + res = ObjCrystBondAngleRestraint( + atom1, atom2, atom3, angle, sigma, delta, scaled + ) + self._restraints.add(res) + + return res + + def restrainBondAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): + """Add a bond angle restraint. + + This creates an instance of ObjCrystBondAngleRestraint and adds it to + the ObjCrystMoleculeParSet. + + Parameters + ---------- + par + A ObjCrystBondAngleParameter (see addBondAngleParameter) + angle + The bond angle (radians) + sigma + The uncertainty of the bond angle (radians) + delta + The width of the bond angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + + Returns + ------- + ObjCrystBondAngleRestraint object + Returns the ObjCrystBondAngleRestraint object for use with the + 'unrestrain' method. + """ + return self.restrainBondAngle( + par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled + ) + + def restrainDihedralAngle( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): + """Add a dihedral angle restraint. + + This creates an instance of ObjCrystDihedralAngleRestraint and adds it + to the ObjCrystMoleculeParSet. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the angle + atom2 + Second (central) atom (ObjCrystMolAtomParSet) in the angle + atom3 + Third (central) atom (ObjCrystMolAtomParSet) in the angle + atom4 + Fourth atom in the angle (ObjCrystMolAtomParSet) + angle + The dihedral angle (radians) + sigma + The uncertainty of the dihedral angle (radians) + delta + The width of the dihedral angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + + Returns + ------- + ObjCrystDihedralAngleRestraint object + Returns the ObjCrystDihedralAngleRestraint object for use with the + 'unrestrain' method. + """ + res = ObjCrystDihedralAngleRestraint( + atom1, atom2, atom3, atom4, angle, sigma, delta, scaled + ) + self._restraints.add(res) + + return res + + def restrainDihedralAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): + """Add a dihedral angle restraint. + + This creates an instance of ObjCrystDihedralAngleRestraint and adds it + to the ObjCrystMoleculeParSet. + + Parameters + ---------- + par + A ObjCrystDihedralAngleParameter (see + addDihedralAngleParameter) + angle + The dihedral angle (radians) + sigma + The uncertainty of the dihedral angle (radians) + delta + The width of the dihedral angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + + Returns + ------- + ObjCrystDihedralAngleRestraint object + Returns the ObjCrystDihedralAngleRestraint object for use with the + 'unrestrain' method. + """ + return self.restrainDihedralAngle( + par.atom1, + par.atom2, + par.atom3, + par.atom4, + angle, + sigma, + delta, + scaled, + ) + + def addBondLengthParameter( + self, name, atom1, atom2, value=None, const=False + ): + """Add a bond length to the Molecule. + + This creates a ObjCrystBondLengthParameter to the + ObjCrystMoleculeParSet that can be adjusted during the fit. + + Parameters + ---------- + name + The name of the ObjCrystBondLengthParameter + atom1 + The first atom (ObjCrystMolAtomParSet) in the bond + atom2 + The second (mutated) atom (ObjCrystMolAtomParSet) in the + bond + value + An initial value for the bond length. If this is None + (default), then the current distance between the atoms will + be used. + const + A flag indicating whether the Parameter is constant + (default False) + + Returns + ------- + ObjCrystBondLengthParameter object + Returns the new ObjCrystBondLengthParameter. + """ + par = ObjCrystBondLengthParameter(name, atom1, atom2, value, const) + self.add_parameter(par) + + return par + + def addBondAngleParameter( + self, name, atom1, atom2, atom3, value=None, const=False + ): + """Add a bond angle to the Molecule. + + This creates a ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet + that can be adjusted during the fit. + + Parameters + ---------- + name + The name of the ObjCrystBondAngleParameter + atom1 + The first atom (ObjCrystMolAtomParSet) in the bond angle + atom2 + The second (central) atom (ObjCrystMolAtomParSet) in the + bond angle + atom3 + The third (mutated) atom (ObjCrystMolAtomParSet) in the + bond angle + value + An initial value for the bond angle. If this is None + (default), then the current bond angle between the atoms + will be used. + const + A flag indicating whether the Parameter is constant + (default False). + + Returns + ------- + ObjCrystBondAngleParameter object + Returns the new ObjCrystBondAngleParameter. + """ + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, value, const + ) + self.add_parameter(par) + + return par + + def addDihedralAngleParameter( + self, name, atom1, atom2, atom3, atom4, value=None, const=False + ): + """Add a dihedral angle to the Molecule. + + This creates a ObjCrystDihedralAngleParameter to the + ObjCrystMoleculeParSet that can be adjusted during the fit. + + Parameters + ---------- + name + The name of the ObjCrystDihedralAngleParameter. + atom1 + The first atom (ObjCrystMolAtomParSet) in the dihderal + angle. + atom2 + The second (central) atom (ObjCrystMolAtomParSet) in the + dihderal angle + atom3 + The third (central) atom (ObjCrystMolAtomParSet) in the + dihderal angle + atom4 + The fourth (mutated) atom (ObjCrystMolAtomParSet) in the + dihderal angle + value + An initial value for the dihedral angle. If this is None + (default), then the current dihedral angle between atoms + will be used. + const + A flag indicating whether the Parameter is constant + (default False). + + Returns + ------- + ObjCrystDihedralAngleParameter object + Returns the new ObjCrystDihedralAngleParameter. + """ + par = ObjCrystDihedralAngleParameter( + name, atom1, atom2, atom3, atom4, value, const + ) + self.add_parameter(par) + + return par + + +# End class ObjCrystMoleculeParSet + + +class ObjCrystMolAtomParSet(ObjCrystScattererParSet): + """A adaptor for an pyobjcryst.molecule.MolAtom. + + This class derives from srfit.fitbase.parameterset.ParameterSet. Note that + MolAtom does not derive from Scatterer, but the relevant interface is the + same within pyobjcryst. See the ParameterSet class for base attributes. + + Attributes + ---------- + scat + The adapted pyobjcryst.molecule.MolAtom. + parent + The ObjCrystCrystalParSet this belongs to + element + Non-refinable name of the element (property). + + Managed Parameters + ------------------ + occ + Occupancy of the atom on its crystal location + (ParameterAdapter) + Biso + Isotropic scattering factor (ParameterAdapter). This does + not exist for dummy atoms. See the 'isDummy' method. + B11, B22, B33, B12, B21, B23, B32, B13, B31 + -- Anisotropic displacement factor for scatterer + (ParameterAdapter or ParameterProxy). Note that the Bij and Bji + parameters are the same. + """ + + def __init__(self, name, scat, parent): + """Initialize. + + Parameters + ---------- + name + The name of the scatterer + scat + The Scatterer instance + parent + The ObjCrystCrystalParSet this belongs to + """ + ObjCrystScattererParSet.__init__(self, name, scat, parent) + sp = scat.GetScatteringPower() + + # Only wrap this if there is a scattering power + if sp is not None: + self.add_parameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.add_parameter(ParameterAdapter("B11", sp, attr="B11")) + self.add_parameter(ParameterAdapter("B22", sp, attr="B22")) + self.add_parameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") + B21 = ParameterProxy("B21", B12) + B13 = ParameterAdapter("B13", sp, attr="B13") + B31 = ParameterProxy("B31", B13) + B23 = ParameterAdapter("B23", sp, attr="B23") + B32 = ParameterProxy("B32", B23) + self.add_parameter(B12) + self.add_parameter(B21) + self.add_parameter(B13) + self.add_parameter(B31) + self.add_parameter(B23) + self.add_parameter(B32) + + return + + def _getelem(self): + """Getter for the element type.""" + sp = self.scat.GetScatteringPower() + if sp: + return sp.GetSymbol() + else: + return "dummy" + + element = property(_getelem) + + def isDummy(self): + """Indicate whether this atom is a dummy atom.""" + return self.scat.IsDummy() + + +# End class ObjCrystMolAtomParSet + + +class ObjCrystMoleculeRestraint(object): + """Base class for adapting pyobjcryst Molecule restraints to srfit. + + The 'penalty' method calls 'GetLogLikelihood' of the pyobjcryst restraint. + This implements the 'penalty' method from + diffpy.srfit.fitbase.restraint.Restraint. The 'restrain' method is not + needed or implemented. + + Attributes + ---------- + res + The pyobjcryst Molecule restraint. + scaled + A flag indicating if the restraint is scaled (multiplied) by + the unrestrained point-average chi^2 (chi^2/numpoints) (default + False). + """ + + def __init__(self, res, scaled=False): + """Create a Restraint-like from a pyobjcryst Molecule restraint. + + Parameters + ---------- + res + The pyobjcryst Molecule restraint. + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + """ + self.res = res + self.scaled = scaled + return + + def penalty(self, w=1.0): + """Calculate the penalty of the restraint. + + Parameters + ---------- + w + The point-average chi^2 which is optionally used to scale the + penalty (default 1.0). + """ + penalty = self.res.GetLogLikelihood() + if self.scaled: + penalty *= w + return penalty + + +# End class ObjCrystMoleculeRestraint + + +class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): + """Restrain the distance between two atoms. + + Attributes + ---------- + atom1 + The first atom in the bond (ObjCrystMolAtomParSet) + atom2 + The second atom in the bond (ObjCrystMolAtomParSet) + length + The length of the bond (Angstroms) + sigma + The uncertainty of the bond length (Angstroms) + delta + The width of the bond (Angstroms) + res + The pyobjcryst BondLength restraint + scaled + A flag indicating if the restraint is scaled (multiplied) by + the unrestrained point-average chi^2 (chi^2/numpoints) (default + False) + """ + + def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): + """Create a bond length restraint. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the bond + atom2 + Second atom (ObjCrystMolAtomParSet) in the bond + length + The length of the bond (Angstroms) + sigma + The uncertainty of the bond length (Angstroms) + delta + The width of the bond (Angstroms) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False) + """ + self.atom1 = atom1 + self.atom2 = atom2 + + m = self.atom1.scat.GetMolecule() + res = m.AddBond(atom1.scat, atom2.scat, length, sigma, delta) + + ObjCrystMoleculeRestraint.__init__(self, res, scaled) + return + + # Give access to the parameters of the restraint + length = property( + lambda self: self.res.GetLength0(), + lambda self, val: self.res.SetLength0(val), + ) + sigma = property( + lambda self: self.res.GetLengthSigma(), + lambda self, val: self.res.SetLengthSigma(val), + ) + delta = property( + lambda self: self.res.GetLengthDelta(), + lambda self, val: self.res.SetLengthDelta(val), + ) + + +# End class ObjCrystBondLengthRestraint + + +class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): + """Restrain the angle defined by three atoms. + + Attributes + ---------- + atom1 + The first atom in the angle (ObjCrystMolAtomParSet) + atom2 + The second atom in the angle (ObjCrystMolAtomParSet) + atom3 + The third atom in the angle (ObjCrystMolAtomParSet) + angle + The bond angle (radians) + sigma + The uncertainty of the bond angle (radians) + delta + The width of the bond angle (radians) + res + The pyobjcryst BondAngle restraint + scaled + A flag indicating if the restraint is scaled (multiplied) by + the unrestrained point-average chi^2 (chi^2/numpoints) (default + False) + """ + + def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): + """Create a bond angle restraint. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the bond angle + atom2 + Second (central) atom (ObjCrystMolAtomParSet) in the bond + angle + atom3 + Third atom (ObjCrystMolAtomParSet) in the bond angle + angle + The bond angle (radians) + sigma + The uncertainty of the bond angle (radians) + delta + The width of the bond angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + """ + self.atom1 = atom1 + self.atom2 = atom2 + self.atom3 = atom3 + + m = self.atom1.scat.GetMolecule() + res = m.AddBondAngle( + atom1.scat, atom2.scat, atom3.scat, angle, sigma, delta + ) + + ObjCrystMoleculeRestraint.__init__(self, res, scaled) + return + + # Give access to the parameters of the restraint + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + + +# End class ObjCrystBondAngleRestraint + + +class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): + """Restrain the dihedral (torsion) angle defined by four atoms. + + Attributes + ---------- + atom1 + The first atom in the angle (ObjCrystMolAtomParSet) + atom2 + The second (central) atom in the angle (ObjCrystMolAtomParSet) + atom3 + The third (central) atom in the angle (ObjCrystMolAtomParSet) + atom4 + The fourth atom in the angle (ObjCrystMolAtomParSet) + angle + The dihedral angle (radians) + sigma + The uncertainty of the dihedral angle (radians) + delta + The width of the dihedral angle (radians) + res + The pyobjcryst DihedralAngle restraint + scaled + A flag indicating if the restraint is scaled (multiplied) by + the unrestrained point-average chi^2 (chi^2/numpoints) (default + False) + """ + + def __init__( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): + """Create a dihedral angle restraint. + + Parameters + ---------- + atom1 + First atom (ObjCrystMolAtomParSet) in the angle + atom2 + Second (central) atom (ObjCrystMolAtomParSet) in the angle + atom3 + Third (central) atom (ObjCrystMolAtomParSet) in the angle + atom4 + Fourth atom in the angle (ObjCrystMolAtomParSet) + angle + The dihedral angle (radians) + sigma + The uncertainty of the dihedral angle (radians) + delta + The width of the dihedral angle (radians) + scaled + A flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + """ + self.atom1 = atom1 + self.atom2 = atom2 + self.atom3 = atom3 + self.atom4 = atom4 + + m = self.atom1.scat.GetMolecule() + res = m.AddDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat, angle, sigma, delta + ) + + ObjCrystMoleculeRestraint.__init__(self, res, scaled) + return + + # Give access to the parameters of the restraint + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + + +# End class ObjCrystDihedralAngleRestraint + + +class StretchModeParameter(Parameter): + """Partial Parameter class encapsulating pyobjcryst stretch modes. + + This class relies upon attributes that do not belong to it. Do not + instantiate this class. + + Required attributes + ------------------- + matoms + The set of all mutated AtomParSets + molecule + The ObjCrystMoleculeParSet the atoms belong to + mode + The pyobjcryst.molecule.StretchMode used to change atomic + positions. + keepcenter + Flag indicating whether to keep the center of mass of the + molecule stationary within the crystal when changing the + value of the parameter (bool, default True). + """ + + def __init__(self, name, value=None, const=False): + """Initialization. + + Parameters + ---------- + name + The name of this Parameter (must be a valid attribute + identifier) + value + The initial value of this Parameter (default 0). + const + A flag inticating whether the Parameter is a constant (like + pi). + + Raises ValueError if the name is not a valid attribute identifier + """ + Parameter.__init__(self, name, value, const) + self.keepcenter = True + + def set_value(self, val): + """Change the value of the Parameter.""" + curval = self.get_value() + val = float(val) + + if val == curval: + return self + + # The StretchMode expects the change in mutated value. + delta = val - curval + self.mode.Stretch(delta, self.keepcenter) + + # Let Parameter take care of the general details + Parameter.set_value(self, val) + + return self + + def addAtoms(self, atomlist): + """Associate ObjCrystMolAtomParSets with the Parameter. + + This will associate additional ObjCrystMolAtomParSets with the + Parameter. These will be mutated in the exact same way as the + primary mutated ObjCrystMolAtomParSet. This is useful when a + group of atoms should move rigidly in response to a change in a + bond property. + """ + if not hasattr(atomlist, "__iter__"): + atomlist = [atomlist] + # Record the added atoms in the Parameter + self.matoms.update(atomlist) + # Make sure we're observing these atoms + for a in atomlist: + a.x.addObserver(self._flush) + a.y.addObserver(self._flush) + a.z.addObserver(self._flush) + + # Record the added atoms in the StretchMode + scatlist = [a.scat for a in atomlist] + self.mode.AddAtoms(scatlist) + return self + + def notify(self, other=()): + """Notify all mutated Parameters and observers. + + Some of the mutated parameters will be observing us. At the same + time we need to observe them. Observable won't let us do both, + so we notify the Parameters that we mutate directly. + """ + noneother = () + # Notify the atoms that have moved + for a in self.matoms: + a.x._flush(noneother) + a.y._flush(noneother) + a.z._flush(noneother) + # Notify the molecule position + self.molecule.x._flush(noneother) + self.molecule.y._flush(noneother) + self.molecule.z._flush(noneother) + + # Notify observers + Parameter.notify(self, other) + return + + +# End class StretchModeParameter + + +class ObjCrystBondLengthParameter(StretchModeParameter): + """Class for abstracting a bond length in a Molecule to a Parameter. + + This wraps up a pyobjcryst.molecule.StretchModeBondLength object so that + the distance between two MolAtoms in a Molecule can be used as an + adjustable Parameter. When a bond length is adjusted, the second MolAtom is + moved, and the absolute position of the Molecule is altered to preserve the + location of the center of mass within the Crystal. Thus, the x, y and z + Parameters of the MolAtom and its parent Molecule are altered. This can be + changed by setting the 'keepcenter' attribute of the parameter to False. + + This Parameter makes it possible to mutate a MolAtom multiple times in a + single refinement step. If these mutations are not orthogonal, then this + could lead to nonconvergence of a fit, depending on the optimizer. Consider + mutating atom2 of a bond directly, and via a ObjCrystBondLengthParameter. + The two mutations of atom2 may be determined independently by the + optimizer, in which case the composed mutation will have an unexpected + effect on the residual. It is best practice to either modify MolAtom + positions directly, or thorough BondLengthParameters, BondAngleParameters + and DihedralAngleParameters (which are mutually orthogonal). + + Note that by making a ObjCrystBondLengthParameter constant it also makes + the underlying ObjCrystMolAtomParSets constant. When setting it as + nonconstant, each ObjCrystMolAtomParSet is set nonconstant. Changing the + bond length changes the position of the second MolAtom and Molecule, even + if either is set as constant. + + Attributes + ---------- + atom1 + The first ObjCrystMolAtomParSet in the bond + atom2 + The second (mutated) ObjCrystMolAtomParSet in the bond + matoms + The set of all mutated ObjCrystMolAtomParSets + molecule + The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets + belong to + mode + The pyobjcryst.molecule.StretchModeBondLength for the bond + + Inherited Attributes + -------------------- + name + A name for this Parameter. + const + A flag indicating whether this is considered a constant. + _value + The value of the Parameter. Modified with 'set_value'. + value + Property for 'get_value' and 'set_value'. + constraint + A callable that calculates the value of this Parameter. If + this is None (None), the the Parameter is responsible for its + own value. The callable takes no arguments. + bounds + A 2-list defining the bounds on the Parameter. This can be + used by some optimizers when the Parameter is varied. + """ + + def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): + """Create a ObjCrystBondLengthParameter. + + Parameters + ---------- + name + The name of the ObjCrystBondLengthParameter + atom1 + The first atom (ObjCrystMolAtomParSet) in the bond + atom2 + The second (mutated) atom (ObjCrystMolAtomParSet) in the + bond + value + An initial value for the bond length. If this is None + (default), then the current distance between the atoms will + be used. + const + A flag indicating whether the Parameter is constant + (default False) + mode + An extant pyobjcryst.molecule.StretchModeBondLength to use. + If this is None (default), then a new StretchModeBondLength + will be built. + """ + # Create the mode + self.mode = mode + if mode is None: + self.mode = StretchModeBondLength(atom1.scat, atom2.scat, None) + # We only add the last atom. This is the one that will move + self.mode.AddAtom(atom2.scat) + self.matoms = set([atom2]) + + # Observe the atom positions + for a in [atom1, atom2]: + a.x.addObserver(self._flush) + a.y.addObserver(self._flush) + a.z.addObserver(self._flush) + + self.atom1 = atom1 + self.atom2 = atom2 + self.molecule = atom1.parent + + # We do this last so the atoms are defined before we set any values. + if value is None: + value = GetBondLength(atom1.scat, atom2.scat) + StretchModeParameter.__init__(self, name, value, const) + self.set_constant(const) + + return + + def set_constant(self, is_constant=True, value=None): + """Toggle the Parameter as constant. + + This sets the underlying ObjCrystMolAtomParSet positions + constant as well. + + Parameters + ---------- + is_constant + Flag indicating if the Parameter is constant (default + True). + value + An optional value for the Parameter (default None). If this + is not None, then the Parameter will get a new value, + constant or otherwise. + + Return + ------ + self + Returns self so that mutators can be chained. + """ + StretchModeParameter.set_constant(self, is_constant, value) + + for a in [self.atom1, self.atom2]: + a.x.set_constant(is_constant) + a.y.set_constant(is_constant) + a.z.set_constant(is_constant) + return self + + @deprecated(bl_setConst_dep_msg) + def setConst(self, const=True, value=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use + diffpy.srfit.structure.objcryst.ObjCrystBondLengthParameter.set_constant + instead. + """ + self.set_constant(const, value) + return self + + def get_value(self): + """This calculates the value if it might have been changed. + + There is no guarantee that the ObjCrystMolAtomParSets underlying + the bond won't change, so the bond length is calculated if + necessary each time this is called. + """ + if self._value is None: + val = GetBondLength(self.atom1.scat, self.atom2.scat) + Parameter.set_value(self, val) + + return self._value + + +# End class ObjCrystBondLengthParameter + + +class ObjCrystBondAngleParameter(StretchModeParameter): + """Class for abstracting a bond angle in a Molecule to a Parameter. + + This wraps up a pyobjcryst.molecule.StretchModeBondAngle object so that the + angle defined by three MolAtoms in a Molecule can be used as an adjustable + Parameter. When a bond angle is adjusted, the third MolAtom is moved, and + the absolute position of the Molecule is altered to preserve the location + of the center of mass within the crystal. This can be changed by setting + the 'keepcenter' attribute of the parameter to False. + + See precautions in the ObjCrystBondLengthParameter class. + + Attributes + ---------- + atom1 + The first ObjCrystAtomParSet in the bond angle + atom2 + The second (central) ObjCrystMolAtomParSet in the bond angle + atom3 + The third (mutated) ObjCrystMolAtomParSet in the bond angle + matoms + The set of all mutated ObjCrystMolAtomParSets + molecule + The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets + belong to + mode + The pyobjcryst.molecule.StretchModeBondAngle for the bond angle + + Inherited Attributes + -------------------- + name + A name for this Parameter. + const + A flag indicating whether this is considered a constant. + _value + The value of the Parameter. Modified with 'set_value'. + value + Property for 'get_value' and 'set_value'. + constraint + A callable that calculates the value of this Parameter. If + this is None (None), the the Parameter is responsible for its + own value. The callable takes no arguments. + bounds + A 2-list defining the bounds on the Parameter. This can be + used by some optimizers when the Parameter is varied. + """ + + def __init__( + self, name, atom1, atom2, atom3, value=None, const=False, mode=None + ): + """Create a ObjCrystBondAngleParameter. + + Parameters + ---------- + name + The name of the ObjCrystBondAngleParameter. + atom1 + The first atom (ObjCrystMolAtomParSet) in the bond angle + atom2 + The second (central) atom (ObjCrystMolAtomParSet) in the + bond angle + atom3 + The third (mutated) atom (ObjCrystMolAtomParSet) in the + bond angle + value + An initial value for the bond length. If this is None + (default), then the current bond angle between the atoms + will be used. + const + A flag indicating whether the Parameter is constant + (default False). + mode + A pre-built mode to place in this Parameter. If this is + None (default), then a StretchMode will be built. + """ + # Create the stretch mode + self.mode = mode + if mode is None: + self.mode = StretchModeBondAngle( + atom1.scat, atom2.scat, atom3.scat, None + ) + # We only add the last atom. This is the one that will move + self.mode.AddAtom(atom3.scat) + self.matoms = set([atom3]) + + # Observe the atom positions + for a in [atom1, atom2, atom3]: + a.x.addObserver(self._flush) + a.y.addObserver(self._flush) + a.z.addObserver(self._flush) + + self.atom1 = atom1 + self.atom2 = atom2 + self.atom3 = atom3 + self.molecule = atom1.parent + + # We do this last so the atoms are defined before we set any values. + if value is None: + value = GetBondAngle(atom1.scat, atom2.scat, atom3.scat) + StretchModeParameter.__init__(self, name, value, const) + self.set_constant(const) + + return + + def set_constant(self, is_constant=True, value=None): + """Toggle the Parameter as constant. + + This sets the underlying ObjCrystMolAtomParSet positions + constant as well. + + Parameters + ---------- + is_constant + Flag indicating if the Parameter is constant (default + True). + value + An optional value for the Parameter (default None). If this + is not None, then the Parameter will get a new value, + constant or otherwise. + + Return + ------ + self + Returns self so that mutators can be chained. + """ + StretchModeParameter.set_constant(self, is_constant, value) + for a in [self.atom1, self.atom2, self.atom3]: + a.x.set_constant(is_constant) + a.y.set_constant(is_constant) + a.z.set_constant(is_constant) + return self + + @deprecated(ba_setConst_dep_msg) + def setConst(self, const=True, value=None): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.structure.objcryst.ObjCrystBondAngleParameter.set_constant + instead. + """ + self.set_constant(const, value) + return self + + def get_value(self): + """This calculates the value if it might have been changed. + + There is no guarantee that the MolAtoms underlying the bond + angle won't change, so the bond angle is calculated if necessary + each time this is called. + """ + if self._value is None: + val = GetBondAngle( + self.atom1.scat, self.atom2.scat, self.atom3.scat + ) + Parameter.set_value(self, val) + + return self._value + + +# End class ObjCrystBondAngleParameter + + +class ObjCrystDihedralAngleParameter(StretchModeParameter): + """Class for abstracting a dihedral angle in a Molecule to a + Parameter. + + This wraps up a pyobjcryst.molecule.StretchModeTorsion object so that the + angle defined by four MolAtoms ([a1-a2].[a3-a4]) in a Molecule can be used + as an adjustable parameter. When a dihedral angle is adjusted, the fourth + MolAtom is moved, and the absolute position of the Molecule is altered to + preserve the location of the center of mass within the crystal. This can + be changed by setting the 'keepcenter' attribute of the parameter to False. + + See precautions in the ObjCrystBondLengthParameter class. + + Attributes + ---------- + atom1 + The first ObjCrystMolAtomParSet in the dihedral angle + atom2 + The second (central) ObjCrystMolAtomParSet in the dihedral + angle + atom3 + The third (central) ObjCrystMolAtomParSet in the dihedral angle + atom4 + The fourth (mutated) ObjCrystMolAtomParSet in the dihedral + angle + matoms + The set of all mutated ObjCrystMolAtomParSets + molecule + The ObjCrystMoleculeParSet the atoms belong to + mode + The pyobjcryst.molecule.StretchModeTorsion for the dihedral + angle + + Inherited Attributes + -------------------- + name + A name for this Parameter. + const + A flag indicating whether this is considered a constant. + _value + The value of the Parameter. Modified with 'set_value'. + value + Property for 'get_value' and 'set_value'. + constraint + A callable that calculates the value of this Parameter. If + this is None (None), the the Parameter is responsible for its + own value. The callable takes no arguments. + bounds + A 2-list defining the bounds on the Parameter. This can be + used by some optimizers when the Parameter is varied. + """ + + def __init__( + self, + name, + atom1, + atom2, + atom3, + atom4, + value=None, + const=False, + mode=None, + ): + """Create a ObjCrystDihedralAngleParameter. + + Parameters + ---------- + name + The name of the ObjCrystDihedralAngleParameter + atom1 + The first atom (ObjCrystMolAtomParSet) in the dihderal + angle + atom2 + The second (central) atom (ObjCrystMolAtomParSet) in the + dihderal angle + atom3 + The third (central) atom (ObjCrystMolAtomParSet) in the + dihderal angle + atom4 + The fourth (mutated) atom (ObjCrystMolAtomParSet) in the + dihderal angle + value + An initial value for the bond length. If this is None + (default), then the current dihedral angle between atoms + will be used. + const + A flag indicating whether the Parameter is constant + (default False). + mode + A pre-built mode to place in this Parameter. If this is + None (default), then a StretchMode will be built. + """ + # Create the stretch mode + self.mode = mode + if mode is None: + self.mode = StretchModeTorsion(atom2.scat, atom3.scat, None) + # We only add the last atom. This is the one that will move + self.mode.AddAtom(atom4.scat) + self.matoms = set([atom4]) + + # Observe the atom positions + for a in [atom1, atom2, atom3, atom4]: + a.x.addObserver(self._flush) + a.y.addObserver(self._flush) + a.z.addObserver(self._flush) + + self.atom1 = atom1 + self.atom2 = atom2 + self.atom3 = atom3 + self.atom4 = atom4 + self.molecule = atom1.parent + + # We do this last so the atoms are defined before we set any values. + if value is None: + value = GetDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat + ) + StretchModeParameter.__init__(self, name, value, const) + self.set_constant(const) + + return + + def set_constant(self, is_constant=True, value=None): + """Toggle the Parameter as constant. + + This sets the underlying ObjCrystMolAtomParSet positions const as well. + + Parameters + ---------- + is_constant + Flag indicating if the Parameter is constant (default + True). + value + An optional value for the Parameter (default None). If this + is not None, then the Parameter will get a new value, + constant or otherwise. + + Return + ------ + self + Returns self so that mutators can be chained. + """ + StretchModeParameter.set_constant(self, is_constant, value) + for a in [self.atom1, self.atom2, self.atom3, self.atom4]: + a.x.set_constant(is_constant) + a.y.set_constant(is_constant) + a.z.set_constant(is_constant) + return self + + @deprecated(da_setConst_dep_msg) + def setConst(self, const=True, value=None): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.structure.objcryst.ObjCrystDihedralAngleParameter.set_constant + instead. + """ + self.set_constant(const, value) + return self + + def get_value(self): + """This calculates the value if it might have been changed. + + There is no guarantee that the ObjCrystMolAtomParSets underlying + the dihedral angle won't change from some other Parameter, so + the value is recalculated each time. + """ + if self._value is None: + val = GetDihedralAngle( + self.atom1.scat, + self.atom2.scat, + self.atom3.scat, + self.atom4.scat, + ) + Parameter.set_value(self, val) + + return self._value + + +# End class ObjCrystDihedralAngleParameter + + +class ObjCrystCrystalParSet(SrRealParSet): + """A adaptor for pyobjcryst.crystal.Crystal instance. + + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. + See this class for base attributes. + + Attributes + ---------- + stru + The adapted pyobjcryst.Crystal. + scatterers + The list of aggregated ScattererParSets (either + ObjCrystAtomParSet or ObjCrystMoleculeParSet), provided for + convenience. + _sgpars + A BaseSpaceGroupParameters object containing free structure + Parameters. See the diffpy.srfit.structure.sgconstraints + module. + sgpars + property that creates _sgpars when it is needed. + angunits + "rad", the units of angle + + Parameters + ---------- + x + Scatterer position in crystal coordinates (ParameterWraper) + y + Scatterer position in crystal coordinates (ParameterWraper) + z + Scatterer position in crystal coordinates (ParameterWraper) + occ + Occupancy of the scatterer on its crystal site (ParameterWraper) + """ + + def __init__(self, name, cryst): + """Initialize. + + Parameters + ---------- + name + A name for this ParameterSet + cryst + An pyobjcryst.Crystal instance. + """ + SrRealParSet.__init__(self, name) + self.angunits = "rad" + self.stru = cryst + self._sgpars = None + + self.add_parameter(ParameterAdapter("a", self.stru, attr="a")) + self.add_parameter(ParameterAdapter("b", self.stru, attr="b")) + self.add_parameter(ParameterAdapter("c", self.stru, attr="c")) + self.add_parameter(ParameterAdapter("alpha", self.stru, attr="alpha")) + self.add_parameter(ParameterAdapter("beta", self.stru, attr="beta")) + self.add_parameter(ParameterAdapter("gamma", self.stru, attr="gamma")) + + # Now we must loop over the scatterers and create parameter sets from + # them. + self.scatterers = [] + snames = [] + + for j in range(self.stru.GetNbScatterer()): + s = self.stru.GetScatt(j) + name = s.GetName() + if not name: + raise ValueError("Each Scatterer must have a name") + if name in snames: + raise ValueError("Scatterer name '%s' is duplicated" % name) + + # Now create the proper object + cname = s.GetClassName() + if cname == "Atom": + parset = ObjCrystAtomParSet(name, s, self) + elif cname == "Molecule": + parset = ObjCrystMoleculeParSet(name, s, self) + else: + raise TypeError("Unrecognized scatterer '%s'" % cname) + + self.add_parameter_set(parset) + self.scatterers.append(parset) + snames.append(name) + + return + + def _constrain_space_group(self): + """Constrain the space group.""" + if self._sgpars is not None: + return self._sgpars + sg = self._create_space_group(self.stru.GetSpaceGroup()) + from diffpy.srfit.structure.sgconstraints import ( + _constrain_as_space_group, + ) + + adpsymbols = ["B11", "B22", "B33", "B12", "B13", "B23"] + isosymbol = "Biso" + sgoffset = [0, 0, 0] + self._sgpars = _constrain_as_space_group( + self, + sg, + self.scatterers, + sgoffset, + adpsymbols=adpsymbols, + isosymbol=isosymbol, + ) + return self._sgpars + + sgpars = property(_constrain_space_group) + + @staticmethod + def _create_space_group(sgobjcryst): + """Create a diffpy.structure SpaceGroup object from pyobjcryst. + + Parameters + ---------- + sgobjcryst + A pyobjcryst.spacegroup.SpaceGroup instance. + + This uses the actual space group operations from the + pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity + about the actual space group. + """ + import copy + + from diffpy.structure.spacegroups import GetSpaceGroup, SymOp + + name = sgobjcryst.GetName() + extnstr = ":%s" % sgobjcryst.GetExtension() + if name.endswith(extnstr): + name = name[: -len(extnstr)] + + # Get whatever spacegroup we can get by name. This will set the proper + # crystal system. Creating a copy of the singleton from GetSpaceGroup, + # as this function messes with sg.symop_list. + sg = copy.copy(GetSpaceGroup(name)) + + # Replace the symmetry operations to guarantee that we get it right. + symops = sgobjcryst.GetSymmetryOperations() + tranops = sgobjcryst.GetTranslationVectors() + sg.symop_list = [] + + for trans in tranops: + for shift, rot in symops: + tv = trans + shift + tv -= numpy.floor(tv) + sg.symop_list.append(SymOp(rot, tv)) + + if sgobjcryst.IsCentrosymmetric(): + center = sgobjcryst.GetInversionCenter() + for trans in tranops: + for shift, rot in symops: + tv = center - trans - shift + tv -= numpy.floor(tv) + sg.symop_list.append(SymOp(-rot, tv)) + + return sg + + @classmethod + def canAdapt(self, stru): + """Return whether the structure can be adapted by this class.""" + from pyobjcryst.crystal import Crystal + + return isinstance(stru, Crystal) + + def getLattice(self): + """Get the ParameterSet containing the lattice Parameters.""" + return self + + def getScatterers(self): + """Get a list of ParameterSets that represents the scatterers. + + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. + """ + return self.scatterers + + +# End class ObjCrystCrystalParSet diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py new file mode 100644 index 0000000..22d57c8 --- /dev/null +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -0,0 +1,867 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Code to set space group constraints for a crystal structure.""" + +import re + +import numpy + +from diffpy.srfit.fitbase.parameter import ParameterProxy +from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer +from diffpy.utils._deprecator import build_deprecation_message, deprecated + +__all__ = ["constrain_as_space_group", "constrainAsSpaceGroup"] + +removal_version = "4.0.0" +sgconstraints_base = "diffpy.srfit.structure.sgconstraints" + +constrainAsSpaceGroup_dep_msg = build_deprecation_message( + sgconstraints_base, + "constrainAsSpaceGroup", + "constrain_as_space_group", + removal_version, +) + + +def constrain_as_space_group( + phase, + spacegroup, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): + """Constrain the structure to the space group. + + This applies space group constraints to a StructureParSet with P1 + symmetry. Passed scatterers are explicitly constrained to the + specified space group. The ADPs and lattice may be constrained as well. + + Parameters + ---------- + phase + A BaseStructure object. + spacegroup + The space group number, symbol or an instance of + SpaceGroup class from diffpy.structure package. + sgoffset + Optional offset for sg origin (default [0, 0, 0]). + scatterers + The scatterer ParameterSets to constrain. If scatterers + is None (default), then all scatterers accessible from + phase.getScatterers will be constrained. + constrainlat + Flag indicating whether to constrain the lattice + (default True). + constrainadps + Flag indicating whether to constrain the ADPs + (default True). + adpsymbols + A list of the ADP names. By default this is equal to + diffpy.structure.symmetryutilities.stdUsymbols (U11, + U22, etc.). The names must be given in the same order + as stdUsymbols. + isosymbol + Symbol for isotropic ADP (default "Uiso"). If None, + isotropic ADPs will be constrained via the anisotropic ADPs. + + + New Parameters that are used in constraints are created within a + SpaceGroupParameters object, which is returned from this function. + Constraints are created in ParameterSet that contains the constrained + Parameter. This will erase any constraints or constant flags on the + scatterers, lattice or ADPs if they are to be constrained. + + The lattice constraints are applied as following. + + Crystal System + Triclinic + No constraints. + Monoclinic + alpha and beta are fixed to 90 unless alpha != beta and + alpha == gamma, in which case alpha and gamma are fixed + to 90. + Orthorhombic + alpha, beta and gamma are fixed to 90. + Tetragonal + b is constrained to a and alpha, beta and gamma are + fixed to 90. + Trigonal + If gamma == 120, then b is constrained to a, alpha + and beta are fixed to 90 and gamma is fixed to 120. + Otherwise, b and c are constrained to a, beta and gamma + are fixed to alpha. + Hexagonal + b is constrained to a, alpha and beta are fixed to 90 + and gamma is fixed to 120. + Cubic + b and c are constrained to a, and alpha, beta and + gamma are fixed to 90. + """ + from diffpy.structure.spacegroups import GetSpaceGroup, SpaceGroup + + sg = spacegroup + if not isinstance(spacegroup, SpaceGroup): + sg = GetSpaceGroup(spacegroup) + sgp = _constrain_as_space_group( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) + + return sgp + + +@deprecated(constrainAsSpaceGroup_dep_msg) +def constrainAsSpaceGroup( + phase, + spacegroup, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): + """This function is deprecated and will be removed in version + 4.0.0. + + Please use + diffpy.srfit.structure.sgconstraints.constrain_as_space_group + instead. + """ + return constrain_as_space_group( + phase, + spacegroup, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) + + +def _constrain_as_space_group( + phase, + sg, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): + """Restricted interface to constrain_as_space_group. + + Arguments: As constrain_as_space_group, except + ----------------------------------------------- + sg + diffpy.structure.spacegroups.SpaceGroup instance + """ + from diffpy.structure.symmetryutilities import stdUsymbols + + if scatterers is None: + scatterers = phase.getScatterers() + if adpsymbols is None: + adpsymbols = stdUsymbols + + sgp = SpaceGroupParameters( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) + + return sgp + + +# End constrain_as_space_group + + +class BaseSpaceGroupParameters(RecipeContainer): + """Base class for holding space group Parameters. + + This class is used to store the variable Parameters of a structure, leaving + out those that constrained or fixed due to space group. This class has the + same Parameter attribute access of a ParameterSet. The purpose of this + class is to make it easy to access the free variables of a structure for + scripting purposes. + + Attributes + ---------- + name + "sgpars" + """ + + def __init__(self, name="sgpars"): + """Create the BaseSpaceGroupParameters object. + + This initializes the attributes. + """ + RecipeContainer.__init__(self, name) + return + + def addParameter(self, par, check=True): + """Store a Parameter. + + Parameters + ---------- + par + The Parameter to be stored. + check + If True (default), a ValueError is raised a Parameter of + the specified name has already been inserted. + + Raises ValueError if the Parameter has no name. + """ + # Store the Parameter + RecipeContainer._add_object(self, par, self._parameters, check) + return + + +# End class BaseSpaceGroupParameters + + +class SpaceGroupParameters(BaseSpaceGroupParameters): + """Class for holding and creating space group Parameters. + + This class is used to store the variable Parameters of a structure, leaving + out those that constrained or fixed due to space group. This does the work + of the constrain_as_space_group method. This class has the same Parameter + attribute access of a ParameterSet. + + Attributes + ---------- + name + "sgpars" + phase + The constrained BaseStructure object. + sg + The diffpy.structure.spacegroups.SpaceGroup object + corresponding to the space group. + sgoffset + Optional offset for the space group origin. + scatterers + The constrained scatterer ParameterSets. + constrainlat + Flag indicating whether the lattice is constrained. + constrainadps + Flag indicating whether the ADPs are constrained. + adpsymbols + A list of the ADP names. + _xyzpars + BaseSpaceGroupParameters of free xyz Parameters that are + constrained to. + xyzpars + Property that populates _xyzpars. + _latpars + BaseSpaceGroupParameters of free lattice Parameters that + are constrained to. + latpars + Property that populates _latpars. + _adppars + BaseSpaceGroupParameters of free ADPs that are constrained + to. + adppars + Property that populates _adppars. + """ + + def __init__( + self, + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ): + """Create the SpaceGroupParameters object. + + Parameters + ---------- + phase + A BaseStructure object to be constrained. + sg + The space group number or symbol (compatible with + diffpy.structure.spacegroups.GetSpaceGroup. + sgoffset + Optional offset for sg origin. + scatterers + The scatterer ParameterSets to constrain. If scatterers + is None, then all scatterers accessible from + phase.getScatterers will be constrained. + constrainlat + Flag indicating whether to constrain the lattice. + constrainadps + Flag indicating whether to constrain the ADPs. + adpsymbols + A list of the ADP names. The names must be given in the + same order as + diffpy.structure.symmetryutilities.stdUsymbols. + isosymbol + Symbol for isotropic ADP (default "Uiso"). If None, + isotropic ADPs will be constrained via the anisotropic + ADPs. + """ + BaseSpaceGroupParameters.__init__(self) + self._latpars = None + self._xyzpars = None + self._adppars = None + + self._parsets = {} + self._manage(self._parsets) + + self.phase = phase + self.sg = sg + self.sgoffset = sgoffset + self.scatterers = scatterers + self.constrainlat = constrainlat + self.constrainadps = constrainadps + self.adpsymbols = adpsymbols + self.isosymbol = isosymbol + + return + + def __iter__(self): + """Iterate over top-level parameters.""" + if ( + self._latpars is None + or self._xyzpars is None + or self._adppars is None + ): + self._make_constraints() + return RecipeContainer.__iter__(self) + + latpars = property(lambda self: self._get_lat_pars()) + + def _get_lat_pars(self): + """Accessor for _latpars.""" + if self._latpars is None: + self._constrain_lattice() + return self._latpars + + xyzpars = property(lambda self: self._get_xyz_pars()) + + def _get_xyz_pars(self): + """Accessor for _xyzpars.""" + positions = [] + for scatterer in self.scatterers: + xyz = [scatterer.x, scatterer.y, scatterer.z] + positions.append([p.value for p in xyz]) + if self._xyzpars is None: + self._constrain_xyzs(positions) + return self._xyzpars + + adppars = property(lambda self: self._get_adp_pars()) + + def _get_adp_pars(self): + """Accessor for _adppars.""" + positions = [] + for scatterer in self.scatterers: + xyz = [scatterer.x, scatterer.y, scatterer.z] + positions.append([p.value for p in xyz]) + if self._adppars is None: + self._constrain_adps(positions) + return self._adppars + + def _make_constraints(self): + """Constrain the structure to the space group. + + This works as described by the constrain_as_space_group method. + """ + # Start by clearing the constraints + self._clear_constraints() + + scatterers = self.scatterers + + # Prepare positions + positions = [] + for scatterer in scatterers: + xyz = [scatterer.x, scatterer.y, scatterer.z] + positions.append([p.value for p in xyz]) + + self._constrain_lattice() + self._constrain_xyzs(positions) + self._constrain_adps(positions) + + return + + def _clear_constraints(self): + """Clear old constraints. + + This only clears constraints where new ones are going to be + applied. + """ + phase = self.phase + scatterers = self.scatterers + isosymbol = self.isosymbol + adpsymbols = self.adpsymbols + + # Clear xyz + for scatterer in scatterers: + + for par in [scatterer.x, scatterer.y, scatterer.z]: + if scatterer.is_constrained(par): + scatterer.remove_constraint(par) + par.set_constant(False) + + # Clear the lattice + if self.constrainlat: + + lattice = phase.getLattice() + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] + for par in latpars: + if lattice.is_constrained(par): + lattice.remove_constraint(par) + par.set_constant(False) + + # Clear ADPs + if self.constrainadps: + for scatterer in scatterers: + if isosymbol: + par = scatterer.get(isosymbol) + if par is not None: + if scatterer.is_constrained(par): + scatterer.remove_constraint(par) + par.set_constant(False) + + for pname in adpsymbols: + par = scatterer.get(pname) + if par is not None: + if scatterer.is_constrained(par): + scatterer.remove_constraint(par) + par.set_constant(False) + + return + + def _constrain_lattice(self): + """Constrain the lattice parameters.""" + if not self.constrainlat: + return + + phase = self.phase + sg = self.sg + + lattice = phase.getLattice() + system = sg.crystal_system + if not system: + system = "Triclinic" + system = system.title() + # This makes the constraints + f = _constraintMap[system] + f(lattice) + + # Now get the unconstrained, non-constant lattice pars and store them. + self._latpars = BaseSpaceGroupParameters("latpars") + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] + pars = [p for p in latpars if not p.const and not p.constrained] + for par in pars: + # FIXME - the original parameter will still appear as + # constrained. + newpar = self.__add_par(par.name, par) + self._latpars.addParameter(newpar) + + return + + def _constrain_xyzs(self, positions): + """Constrain the positions. + + Parameters + ---------- + positions + The coordinates of the scatterers. + """ + from diffpy.structure.symmetryutilities import SymmetryConstraints + + sg = self.sg + sgoffset = self.sgoffset + + # We do this without ADPs here so we can skip much complication. See + # the _constrain_adps method for details. + g = SymmetryConstraints(sg, positions, sgoffset=sgoffset) + + scatterers = self.scatterers + self._xyzpars = BaseSpaceGroupParameters("xyzpars") + + # Make proxies to the free xyz parameters + xyznames = [name[:1] + "_" + name[1:] for name, val in g.pospars] + for pname in xyznames: + name, idx = pname.rsplit("_", 1) + idx = int(idx) + par = scatterers[idx].get(name) + newpar = self.__add_par(pname, par) + self._xyzpars.addParameter(newpar) + + # Constrain non-free xyz parameters + fpos = g.positionFormulas(xyznames) + for idx, tmp in enumerate(zip(scatterers, fpos)): + scatterer, fp = tmp + + # Extract the constraint equation from the formula + for parname, formula in fp.items(): + _makeconstraint( + parname, formula, scatterer, idx, self._parameters + ) + + return + + def _constrain_adps(self, positions): + """Constrain the ADPs. + + Parameters + ---------- + positions + The coordinates of the scatterers. + """ + from diffpy.structure.symmetryutilities import ( + SymmetryConstraints, + stdUsymbols, + ) + + if not self.constrainadps: + return + + sg = self.sg + sgoffset = self.sgoffset + scatterers = self.scatterers + isosymbol = self.isosymbol + adpsymbols = self.adpsymbols + adpmap = dict(zip(stdUsymbols, adpsymbols)) + self._adppars = BaseSpaceGroupParameters("adppars") + + # Prepare ADPs. Note that not all scatterers have constrainable ADPs. + # For example, MoleculeParSet from objcryststructure does not. We + # discard those. + nonadps = [] + Uijs = [] + for sidx, scatterer in enumerate(scatterers): + + pars = [scatterer.get(symb) for symb in adpsymbols] + + if None in pars: + nonadps.append(sidx) + continue + + Uij = numpy.zeros((3, 3), dtype=float) + for idx, par in enumerate(pars): + i, j = _idxtoij[idx] + Uij[i, j] = Uij[j, i] = par.get_value() + + Uijs.append(Uij) + + # Discard any positions for the nonadps + positions = list(positions) + nonadps.reverse() + [positions.pop(idx) for idx in nonadps] + + # Now we can create symmetry constraints without having to worry about + # the nonadps + g = SymmetryConstraints(sg, positions, Uijs, sgoffset=sgoffset) + + adpnames = [adpmap[name[:3]] + "_" + name[3:] for name, val in g.Upars] + + # Make proxies to the free adp parameters. We start by filtering out + # the isotropic ones so we can use the isotropic parameter. + isoidx = [] + isonames = [] + for pname in adpnames: + name, idx = pname.rsplit("_", 1) + idx = int(idx) + # Check for isotropic ADPs + scatterer = scatterers[idx] + if isosymbol and g.Uisotropy[idx] and idx not in isoidx: + isoidx.append(idx) + par = scatterer.get(isosymbol) + if par is not None: + parname = "%s_%i" % (isosymbol, idx) + newpar = self.__add_par(parname, par) + self._adppars.addParameter(newpar) + isonames.append(newpar.name) + else: + par = scatterer.get(name) + if par is not None: + newpar = self.__add_par(pname, par) + self._adppars.addParameter(newpar) + + # Constrain dependent isotropics + for idx, isoname in zip(isoidx[:], isonames): + for j in g.coremap[idx]: + if j == idx: + continue + isoidx.append(j) + scatterer = scatterers[j] + scatterer.add_constraint( + isosymbol, isoname, params=self._parameters + ) + + fadp = g.UFormulas(adpnames) + + # Constrain dependent anisotropics. We use the fact that an + # anisotropic cannot be dependent on an isotropic. + for idx, tmp in enumerate(zip(scatterers, fadp)): + if idx in isoidx: + continue + scatterer, fa = tmp + # Extract the constraint equation from the formula + for stdparname, formula in fa.items(): + pname = adpmap[stdparname] + _makeconstraint( + pname, formula, scatterer, idx, self._parameters + ) + + def __add_par(self, parname, par): + """Constrain a parameter via proxy with a specified name. + + Parameters + ---------- + par + Parameter to constrain + idx + Index to identify scatterer from which par comes + """ + newpar = ParameterProxy(parname, par) + self.addParameter(newpar) + return newpar + + +# End class SpaceGroupParameters + +# crystal system rules +# ref: Benjamin, W. A., Introduction to crystallography, +# New York (1969), p.60 + + +def _constrain_triclinic(lattice): + """Make constraints for Triclinic systems.""" + return + + +def _constrain_monoclinic(lattice): + """Make constraints for Monoclinic systems. + + alpha and beta are fixed to 90 unless alpha != beta and alpha == + gamma, in which case alpha and gamma are constrained to 90. + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + lattice.alpha.set_constant(True, ang90) + beta = lattice.beta.get_value() + gamma = lattice.gamma.get_value() + + if ang90 != beta and ang90 == gamma: + lattice.gamma.set_constant(True, ang90) + else: + lattice.beta.set_constant(True, ang90) + return + + +def _constrain_orthorhombic(lattice): + """Make constraints for Orthorhombic systems. + + alpha, beta and gamma are constrained to 90 + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + lattice.alpha.set_constant(True, ang90) + lattice.beta.set_constant(True, ang90) + lattice.gamma.set_constant(True, ang90) + return + + +def _constrain_tetragonal(lattice): + """Make constraints for Tetragonal systems. + + b is constrained to a and alpha, beta and gamma are constrained to + 90. + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + lattice.alpha.set_constant(True, ang90) + lattice.beta.set_constant(True, ang90) + lattice.gamma.set_constant(True, ang90) + lattice.add_constraint(lattice.b, lattice.a) + return + + +def _constrain_trigonal(lattice): + """Make constraints for Trigonal systems. + + If gamma == 120, then b is constrained to a, alpha and beta are + constrained to 90 and gamma is constrained to 120. Otherwise, b and + c are constrained to a, beta and gamma are constrained to alpha. + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + ang120 = 120.0 * afactor + if lattice.gamma.get_value() == ang120: + lattice.add_constraint(lattice.b, lattice.a) + lattice.alpha.set_constant(True, ang90) + lattice.beta.set_constant(True, ang90) + lattice.gamma.set_constant(True, ang120) + else: + lattice.add_constraint(lattice.b, lattice.a) + lattice.add_constraint(lattice.c, lattice.a) + lattice.add_constraint(lattice.beta, lattice.alpha) + lattice.add_constraint(lattice.gamma, lattice.alpha) + return + + +def _constrain_hexagonal(lattice): + """Make constraints for Hexagonal systems. + + b is constrained to a, alpha and beta are constrained to 90 and + gamma is constrained to 120. + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + ang120 = 120.0 * afactor + lattice.add_constraint(lattice.b, lattice.a) + lattice.alpha.set_constant(True, ang90) + lattice.beta.set_constant(True, ang90) + lattice.gamma.set_constant(True, ang120) + return + + +def _constrain_cubic(lattice): + """Make constraints for Cubic systems. + + b and c are constrained to a, alpha, beta and gamma are constrained + to 90. + """ + afactor = 1 + if lattice.angunits == "rad": + afactor = deg2rad + ang90 = 90.0 * afactor + lattice.add_constraint(lattice.b, lattice.a) + lattice.add_constraint(lattice.c, lattice.a) + lattice.alpha.set_constant(True, ang90) + lattice.beta.set_constant(True, ang90) + lattice.gamma.set_constant(True, ang90) + return + + +# This is used to map the correct crystal system to the proper constraint +# function. +_constraintMap = { + "Triclinic": _constrain_triclinic, + "Monoclinic": _constrain_monoclinic, + "Orthorhombic": _constrain_orthorhombic, + "Tetragonal": _constrain_tetragonal, + "Trigonal": _constrain_trigonal, + "Hexagonal": _constrain_hexagonal, + "Cubic": _constrain_cubic, +} + + +def _makeconstraint(parname, formula, scatterer, idx, ns={}): + """Constrain a parameter according to a formula. + + Parameters + ---------- + parname + Name of parameter + formula + Constraint formula + scatterer + scatterer containing par of parname + idx + Index to identify scatterer from which par comes + ns + namespace to draw extra names from (default {}) + + Returns + ------- + par + Returns the parameter if it is free. + """ + par = scatterer.get(parname) + + if par is None: + return + + compname = "%s_%i" % (parname, idx) + + # Check to see if this parameter is free + pat = r"%s *([+-] *\d+)?$" % compname + if re.match(pat, formula): + return par + + # Check to see if it is a constant + fval = _get_float(formula) + if fval is not None: + par.set_constant() + return + + # If we got here, then we have a constraint equation + # Fix any division issues + formula = formula.replace("/", "*1.0/") + scatterer.add_constraint(par, formula, params=ns) + return + + +def _get_float(formula): + """Get a float from a formula string, or None if this is not + possible.""" + try: + return eval(formula) + except NameError: + return None + + +# Constants needed above +_idxtoij = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] +deg2rad = numpy.pi / 180 +rad2deg = 1.0 / deg2rad + + +# End of file diff --git a/src/diffpy/cmistructure/srrealparset.py b/src/diffpy/cmistructure/srrealparset.py new file mode 100644 index 0000000..e98f06c --- /dev/null +++ b/src/diffpy/cmistructure/srrealparset.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2009 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Chris Farrow +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Structure wrapper class for structures compatible with SrReal.""" + +__all__ = ["SrRealParSet"] + +from diffpy.srfit.structure.basestructureparset import BaseStructureParSet +from diffpy.srfit.structure.bvsrestraint import BVSRestraint + + +class SrRealParSet(BaseStructureParSet): + """Base class for SrReal-compatible structure adapters. + + This derives from BaseStructureParSet and provides some extended + functionality provided by SrReal. + + Attributes + ---------- + stru + The adapted object + _usesymmetry + A flag indicating if SrReal calculators that operate on + this object should use symmetry. By default this is + True. + """ + + def __init__(self, *args, **kw): + BaseStructureParSet.__init__(self, *args, **kw) + self._usesymmetry = True + self.stru = None + return + + def restrainBVS(self, sig=1, scaled=False): + """Restrain the bond-valence sum to zero. + + This adds a penalty to the cost function equal to + bvmsdiff / sig**2 + where bvmsdiff is the mean-squared difference between the calculated + and expected bond valence sums for the structure. If scaled is True, + this is also scaled by the current point-averaged chi^2 value so the + restraint is roughly equally weighted in the fit. + + Parameters + ---------- + sig + The uncertainty on the BVS (default 1). + scaled + A flag indicating if the restraint is scaled + (multiplied) by the unrestrained point-average chi^2 + (chi^2/numpoints) (default False). + + Returns the BVSRestraint object for use with the 'unrestrain' method. + """ + # Create the Restraint object + res = BVSRestraint(self, sig, scaled) + # Add it to the _restraints set + self._restraints.add(res) + # Our configuration changed. Notify observers. + self._update_configuration() + # Return the Restraint object + return res + + def useSymmetry(self, use=True): + """Set this structure to use symmetry. + + This determines how the structure is treated by SrReal + calculators. + """ + self._usesymmetry = bool(use) + return + + def usingSymmetry(self): + """Check if symmetry is being used.""" + return self._usesymmetry + + def _get_srreal_structure(self): + """Get the structure object for use with SrReal calculators. + + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. + """ + from diffpy.srreal.structureadapter import nosymmetry + + if self._usesymmetry: + return self.stru + return nosymmetry(self.stru) diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py new file mode 100644 index 0000000..cecda1c --- /dev/null +++ b/tests/test_diffpyparset.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Tests for diffpy.srfit.structure package.""" + +import pickle +import unittest + +import numpy as np + +from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + + +def testDiffpyStructureParSet(): + """Test the structure conversion.""" + from diffpy.structure import Atom, Lattice, Structure + + a1 = Atom("Cu", xyz=np.array([0.0, 0.1, 0.2]), Uisoequiv=0.003) + a2 = Atom("Ag", xyz=np.array([0.3, 0.4, 0.5]), Uisoequiv=0.002) + lattice = Lattice(2.5, 2.5, 2.5, 90, 90, 90) + + dsstru = Structure([a1, a2], lattice) + # Structure makes copies + a1 = dsstru[0] + a2 = dsstru[1] + + s = DiffpyStructureParSet("CuAg", dsstru) + + assert s.name == "CuAg" + + def _testAtoms(): + # Check the atoms thoroughly + assert a1.element == s.Cu0.element + assert a2.element == s.Ag0.element + assert a1.Uisoequiv == s.Cu0.Uiso.get_value() + assert a2.Uisoequiv == s.Ag0.Uiso.get_value() + assert a1.Bisoequiv == s.Cu0.Biso.get_value() + assert a2.Bisoequiv == s.Ag0.Biso.get_value() + for i in range(1, 4): + for j in range(i, 4): + uijstru = getattr(a1, "U%i%i" % (i, j)) + uij = getattr(s.Cu0, "U%i%i" % (i, j)).get_value() + uji = getattr(s.Cu0, "U%i%i" % (j, i)).get_value() + assert uijstru == uij + assert uijstru == uji + bijstru = getattr(a1, "B%i%i" % (i, j)) + bij = getattr(s.Cu0, "B%i%i" % (i, j)).get_value() + bji = getattr(s.Cu0, "B%i%i" % (j, i)).get_value() + assert bijstru == bij + assert bijstru == bji + + assert a1.xyz[0] == s.Cu0.x.get_value() + assert a1.xyz[1] == s.Cu0.y.get_value() + assert a1.xyz[2] == s.Cu0.z.get_value() + return + + def _testLattice(): + + # Test the lattice + assert dsstru.lattice.a == s.lattice.a.get_value() + assert dsstru.lattice.b == s.lattice.b.get_value() + assert dsstru.lattice.c == s.lattice.c.get_value() + assert dsstru.lattice.alpha == s.lattice.alpha.get_value() + assert dsstru.lattice.beta == s.lattice.beta.get_value() + assert dsstru.lattice.gamma == s.lattice.gamma.get_value() + + _testAtoms() + _testLattice() + + # Now change some values from the diffpy Structure + a1.xyz[1] = 0.123 + a1.U11 = 0.321 + a1.B32 = 0.111 + dsstru.lattice.setLatPar(a=3.0, gamma=121) + _testAtoms() + _testLattice() + + # Now change values from the srfit DiffpyStructureParSet + s.Cu0.x.set_value(0.456) + s.Cu0.U22.set_value(0.441) + s.Cu0.B13.set_value(0.550) + d = dsstru.lattice.dist(a1.xyz, a2.xyz) + s.lattice.b.set_value(4.6) + s.lattice.alpha.set_value(91.3) + _testAtoms() + _testLattice() + # Make sure the distance changed + assert d != dsstru.lattice.dist(a1.xyz, a2.xyz) + return + + +def test___repr__(): + """Test representation of DiffpyStructureParSet objects.""" + from diffpy.structure import Atom, Lattice, Structure + + lat = Lattice(3, 3, 2, 90, 90, 90) + atom = Atom("C", [0, 0.2, 0.5]) + stru = Structure([atom], lattice=lat) + dsps = DiffpyStructureParSet("dsps", stru) + assert repr(stru) == repr(dsps) + assert repr(lat) == repr(dsps.lattice) + assert repr(atom) == repr(dsps.atoms[0]) + return + + +def test_pickling(): + """Test pickling of DiffpyStructureParSet.""" + from diffpy.structure import Atom, Structure + + stru = Structure([Atom("C", [0, 0.2, 0.5])]) + dsps = DiffpyStructureParSet("dsps", stru) + data = pickle.dumps(dsps) + dsps2 = pickle.loads(data) + assert 1 == len(dsps2.atoms) + assert 0.2 == dsps2.atoms[0].y.value + return + + +# End of class TestParameterAdapter + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py new file mode 100644 index 0000000..1edc95b --- /dev/null +++ b/tests/test_objcrystparset.py @@ -0,0 +1,685 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Tests for diffpy.srfit.structure package.""" + +import unittest + +import numpy +import pytest + +# Global variables to be assigned in setUp +ObjCrystCrystalParSet = spacegroups = None +Crystal = Atom = Molecule = ScatteringPowerAtom = None + + +c60xyz = """\ +3.451266498 0.685000000 0.000000000 +3.451266498 -0.685000000 0.000000000 +-3.451266498 0.685000000 0.000000000 +-3.451266498 -0.685000000 0.000000000 +0.685000000 0.000000000 3.451266498 +-0.685000000 0.000000000 3.451266498 +0.685000000 0.000000000 -3.451266498 +-0.685000000 0.000000000 -3.451266498 +0.000000000 3.451266498 0.685000000 +0.000000000 3.451266498 -0.685000000 +0.000000000 -3.451266498 0.685000000 +0.000000000 -3.451266498 -0.685000000 +3.003809890 1.409000000 1.171456608 +3.003809890 1.409000000 -1.171456608 +3.003809890 -1.409000000 1.171456608 +3.003809890 -1.409000000 -1.171456608 +-3.003809890 1.409000000 1.171456608 +-3.003809890 1.409000000 -1.171456608 +-3.003809890 -1.409000000 1.171456608 +-3.003809890 -1.409000000 -1.171456608 +1.409000000 1.171456608 3.003809890 +1.409000000 -1.171456608 3.003809890 +-1.409000000 1.171456608 3.003809890 +-1.409000000 -1.171456608 3.003809890 +1.409000000 1.171456608 -3.003809890 +1.409000000 -1.171456608 -3.003809890 +-1.409000000 1.171456608 -3.003809890 +-1.409000000 -1.171456608 -3.003809890 +1.171456608 3.003809890 1.409000000 +-1.171456608 3.003809890 1.409000000 +1.171456608 3.003809890 -1.409000000 +-1.171456608 3.003809890 -1.409000000 +1.171456608 -3.003809890 1.409000000 +-1.171456608 -3.003809890 1.409000000 +1.171456608 -3.003809890 -1.409000000 +-1.171456608 -3.003809890 -1.409000000 +2.580456608 0.724000000 2.279809890 +2.580456608 0.724000000 -2.279809890 +2.580456608 -0.724000000 2.279809890 +2.580456608 -0.724000000 -2.279809890 +-2.580456608 0.724000000 2.279809890 +-2.580456608 0.724000000 -2.279809890 +-2.580456608 -0.724000000 2.279809890 +-2.580456608 -0.724000000 -2.279809890 +0.724000000 2.279809890 2.580456608 +0.724000000 -2.279809890 2.580456608 +-0.724000000 2.279809890 2.580456608 +-0.724000000 -2.279809890 2.580456608 +0.724000000 2.279809890 -2.580456608 +0.724000000 -2.279809890 -2.580456608 +-0.724000000 2.279809890 -2.580456608 +-0.724000000 -2.279809890 -2.580456608 +2.279809890 2.580456608 0.724000000 +-2.279809890 2.580456608 0.724000000 +2.279809890 2.580456608 -0.724000000 +-2.279809890 2.580456608 -0.724000000 +2.279809890 -2.580456608 0.724000000 +-2.279809890 -2.580456608 0.724000000 +2.279809890 -2.580456608 -0.724000000 +-2.279809890 -2.580456608 -0.724000000 +""" + + +def makeC60(): + """Make a crystal containing the C60 molecule using pyobjcryst.""" + pi = numpy.pi + c = Crystal(100, 100, 100, "P1") + c.SetName("c60frame") + m = Molecule(c, "c60") + + c.AddScatterer(m) + + sp = ScatteringPowerAtom("C", "C") + sp.SetBiso(8 * pi * pi * 0.003) + # c.AddScatteringPower(sp) + + for i, l in enumerate(c60xyz.strip().splitlines()): + x, y, z = map(float, l.split()) + m.AddAtom(x, y, z, sp, "C%i" % i) + + return c + + +# ---------------------------------------------------------------------------- + + +class TestParameterAdapter: + @pytest.fixture(autouse=True) + def setup(self, pyobjcryst_available): + # shared setup + if not pyobjcryst_available: + pytest.skip("pyobjcryst package not available") + + global ObjCrystCrystalParSet, Crystal, Atom, Molecule + global ScatteringPowerAtom + from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal + from pyobjcryst.molecule import Molecule + from pyobjcryst.scatteringpower import ScatteringPowerAtom + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + + self.occryst = makeC60() + self.ocmol = self.occryst.GetScatterer("c60") + return + + def tearDown(self): + del self.occryst + del self.ocmol + return + + def testImplicitBondAngleRestraints(self): + """Test the structure with implicit bond angles.""" + occryst = self.occryst + ocmol = self.ocmol + + # Add some bond angles to the molecule + ocmol.AddBondAngle(ocmol[0], ocmol[5], ocmol[8], 1.1, 0.1, 0.1) + ocmol.AddBondAngle(ocmol[0], ocmol[7], ocmol[44], 1.3, 0.1, 0.1) + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + m.wrapRestraints() + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + res0, res1 = m._restraints + p0 = set([res0.penalty(), res1.penalty()]) + angles = ocmol.GetBondAngleList() + p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) + assert p0 == p1 + + return + + def testObjCrystParSet(self): + """Test the structure conversion.""" + occryst = self.occryst + ocmol = self.ocmol + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + assert cryst.name == "bucky" + + def _testCrystal(): + # Test the lattice + assert occryst.a == pytest.approx(cryst.a.value) + assert occryst.b == pytest.approx(cryst.b.get_value()) + assert occryst.c == pytest.approx(cryst.c.get_value()) + assert occryst.alpha == pytest.approx(cryst.alpha.get_value()) + assert occryst.beta == pytest.approx(cryst.beta.get_value()) + assert occryst.gamma == pytest.approx(cryst.gamma.get_value()) + return + + def _testMolecule(): + + # Test position / occupancy + assert ocmol.X == pytest.approx(m.x.get_value()) + assert ocmol.Y == pytest.approx(m.y.get_value()) + assert ocmol.Z == pytest.approx(m.z.get_value()) + assert ocmol.Occupancy == pytest.approx(m.occ.get_value()) + + # Test orientation + assert ocmol.Q0 == pytest.approx(m.q0.get_value()) + assert ocmol.Q1 == pytest.approx(m.q1.get_value()) + assert ocmol.Q2 == pytest.approx(m.q2.get_value()) + assert ocmol.Q3 == pytest.approx(m.q3.get_value()) + + # Check the atoms thoroughly + for i in range(len(ocmol)): + oca = ocmol[i] + ocsp = oca.GetScatteringPower() + a = m.atoms[i] + assert ocsp.GetSymbol() == a.element + assert oca.X == pytest.approx(a.x.get_value()) + assert oca.Y == pytest.approx(a.y.get_value()) + assert oca.Z == pytest.approx(a.z.get_value()) + assert oca.Occupancy == pytest.approx(a.occ.get_value()) + assert ocsp.Biso == pytest.approx(a.Biso.get_value()) + return + + _testCrystal() + _testMolecule() + + # Now change some values from ObjCryst + ocmol[0].X *= 1.1 + ocmol[0].Occupancy *= 1.1 + ocmol[0].GetScatteringPower().Biso *= 1.1 + ocmol.Q0 *= 1.1 + occryst.a *= 1.1 + + _testCrystal() + _testMolecule() + + # Now change values from the srfit StructureParSet + cryst.c60.C44.x.set_value(1.1) + cryst.c60.C44.occ.set_value(1.1) + cryst.c60.C44.Biso.set_value(1.1) + cryst.c60.q3.set_value(1.1) + cryst.a.set_value(1.1) + + _testCrystal() + _testMolecule() + return + + def testImplicitBondLengthRestraints(self): + """Test the structure with implicit bond lengths.""" + occryst = self.occryst + ocmol = self.ocmol + + # Add some bonds to the molecule + ocmol.AddBond(ocmol[0], ocmol[5], 3.3, 0.1, 0.1) + ocmol.AddBond(ocmol[0], ocmol[7], 3.3, 0.1, 0.1) + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + m.wrapRestraints() + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + res0, res1 = m._restraints + p0 = set([res0.penalty(), res1.penalty()]) + bonds = ocmol.GetBondList() + p1 = set([bonds[0].GetLogLikelihood(), bonds[1].GetLogLikelihood()]) + assert p0 == p1 + + return + + def testImplicitDihedralAngleRestraints(self): + """Test the structure with implicit dihedral angles.""" + occryst = self.occryst + ocmol = self.ocmol + + # Add some bond angles to the molecule + ocmol.AddDihedralAngle( + ocmol[0], ocmol[5], ocmol[8], ocmol[41], 1.1, 0.1, 0.1 + ) + ocmol.AddDihedralAngle( + ocmol[0], ocmol[7], ocmol[44], ocmol[2], 1.3, 0.1, 0.1 + ) + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + m.wrapRestraints() + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + res0, res1 = m._restraints + p0 = set([res0.penalty(), res1.penalty()]) + angles = ocmol.GetDihedralAngleList() + p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) + assert p0 == p1 + + return + + def testImplicitStretchModes(self): + """Test the molecule with implicit stretch modes.""" + # Not sure how to make this happen. + pass + + def testExplicitBondLengthRestraints(self): + """Test the structure with explicit bond lengths.""" + occryst = self.occryst + ocmol = self.ocmol + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + # make some bond angle restraints + res0 = m.restrainBondLength(m.atoms[0], m.atoms[5], 3.3, 0.1, 0.1) + res1 = m.restrainBondLength(m.atoms[0], m.atoms[7], 3.3, 0.1, 0.1) + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + p0 = [res0.penalty(), res1.penalty()] + bonds = ocmol.GetBondList() + assert 2 == len(bonds) + p1 = [b.GetLogLikelihood() for b in bonds] + assert p0 == p1 + + return + + def testExplicitBondAngleRestraints(self): + """Test the structure with explicit bond angles. + + Note that this cannot work with co-linear points as the + direction of rotation cannot be defined in this case. + """ + occryst = self.occryst + ocmol = self.ocmol + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + # restrain some bond angles + res0 = m.restrainBondAngle( + m.atoms[0], m.atoms[5], m.atoms[8], 3.3, 0.1, 0.1 + ) + res1 = m.restrainBondAngle( + m.atoms[0], m.atoms[7], m.atoms[44], 3.3, 0.1, 0.1 + ) + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + p0 = set([res0.penalty(), res1.penalty()]) + angles = ocmol.GetBondAngleList() + p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) + assert p0 == p1 + + return + + def testExplicitDihedralAngleRestraints(self): + """Test the structure with explicit dihedral angles.""" + occryst = self.occryst + ocmol = self.ocmol + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + # Restrain some dihedral angles. + res0 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[5], m.atoms[8], m.atoms[41], 1.1, 0.1, 0.1 + ) + res1 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[7], m.atoms[44], m.atoms[2], 1.1, 0.1, 0.1 + ) + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + p0 = set([res0.penalty(), res1.penalty()]) + angles = ocmol.GetDihedralAngleList() + p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) + assert p0 == p1 + + return + + def testExplicitBondLengthParameter(self): + """Test adding bond length parameters to the molecule.""" + occryst = self.occryst + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + a0 = m.atoms[0] + a7 = m.atoms[7] + a20 = m.atoms[20] + + # Add a parameter + p1 = m.addBondLengthParameter("C07", a0, a7) + # Have another atom tag along for the ride + p1.addAtoms([a20]) + + xyz0 = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7 = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20 = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + + dd = xyz0 - xyz7 + d0 = numpy.dot(dd, dd) ** 0.5 + assert d0 == pytest.approx(p1.get_value(), abs=1e-6) + + # Record the unit direction of change for later + u = dd / d0 + + # Change the value + scale = 1.05 + p1.set_value(scale * d0) + + # Verify that it has changed. + assert scale * d0 == pytest.approx(p1.get_value(), abs=1e-6) + + xyz0a = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7a = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20a = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + + dda = xyz0a - xyz7a + d1 = numpy.dot(dda, dda) ** 0.5 + + assert scale * d0 == pytest.approx(d1, abs=1e-6) + + # Verify that only the second and third atoms have moved. + + assert numpy.array_equal(xyz0, xyz0a) + + xyz7calc = xyz7 + (1 - scale) * d0 * u + for i in range(3): + assert xyz7a[i] == pytest.approx(xyz7calc[i], abs=1e-5) + + xyz20calc = xyz20 + (1 - scale) * d0 * u + for i in range(3): + assert xyz20a[i] == pytest.approx(xyz20calc[i], abs=1e-6) + + return + + def testExplicitBondAngleParameter(self): + """Test adding bond angle parameters to the molecule.""" + occryst = self.occryst + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + a0 = m.atoms[0] + a7 = m.atoms[7] + a20 = m.atoms[20] + a25 = m.atoms[25] + + xyz0 = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7 = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20 = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + xyz25 = numpy.array( + [a25.x.get_value(), a25.y.get_value(), a25.z.get_value()] + ) + + v1 = xyz7 - xyz0 + d1 = numpy.dot(v1, v1) ** 0.5 + v2 = xyz7 - xyz20 + d2 = numpy.dot(v2, v2) ** 0.5 + + angle0 = numpy.arccos(numpy.dot(v1, v2) / (d1 * d2)) + + # Add a parameter + p1 = m.addBondAngleParameter("C0720", a0, a7, a20) + # Have another atom tag along for the ride + p1.addAtoms([a25]) + + assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) + + # Change the value + scale = 1.05 + p1.set_value(scale * angle0) + + # Verify that it has changed. + assert scale * angle0 == pytest.approx(p1.get_value(), abs=1e-6) + + xyz0a = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7a = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20a = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + xyz25a = numpy.array( + [a25.x.get_value(), a25.y.get_value(), a25.z.get_value()] + ) + + v1a = xyz7a - xyz0a + d1a = numpy.dot(v1a, v1a) ** 0.5 + v2a = xyz7a - xyz20a + d2a = numpy.dot(v2a, v2a) ** 0.5 + + angle1 = numpy.arccos(numpy.dot(v1a, v2a) / (d1a * d2a)) + + assert scale * angle0 == pytest.approx(angle1, abs=1e-6) + + # Verify that only the last two atoms have moved. + + assert numpy.array_equal(xyz0, xyz0a) + assert numpy.array_equal(xyz7, xyz7a) + assert not numpy.array_equal(xyz20, xyz20a) + assert not numpy.array_equal(xyz25, xyz25a) + + return + + def testExplicitDihedralAngleParameter(self): + """Test adding dihedral angle parameters to the molecule.""" + occryst = self.occryst + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + + a0 = m.atoms[0] + a7 = m.atoms[7] + a20 = m.atoms[20] + a25 = m.atoms[25] + a33 = m.atoms[33] + + xyz0 = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7 = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20 = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + xyz25 = numpy.array( + [a25.x.get_value(), a25.y.get_value(), a25.z.get_value()] + ) + xyz33 = numpy.array( + [a33.x.get_value(), a33.y.get_value(), a33.z.get_value()] + ) + + v12 = xyz0 - xyz7 + v23 = xyz7 - xyz20 + v34 = xyz20 - xyz25 + v123 = numpy.cross(v12, v23) + v234 = numpy.cross(v23, v34) + + d123 = numpy.dot(v123, v123) ** 0.5 + d234 = numpy.dot(v234, v234) ** 0.5 + angle0 = -numpy.arccos(numpy.dot(v123, v234) / (d123 * d234)) + + # Add a parameter + p1 = m.addDihedralAngleParameter("C072025", a0, a7, a20, a25) + # Have another atom tag along for the ride + p1.addAtoms([a33]) + + assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) + + # Change the value + scale = 1.05 + p1.set_value(scale * angle0) + + # Verify that it has changed. + assert scale * angle0 == pytest.approx(p1.get_value(), abs=1e-6) + + xyz0a = numpy.array( + [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] + ) + xyz7a = numpy.array( + [a7.x.get_value(), a7.y.get_value(), a7.z.get_value()] + ) + xyz20a = numpy.array( + [a20.x.get_value(), a20.y.get_value(), a20.z.get_value()] + ) + xyz25a = numpy.array( + [a25.x.get_value(), a25.y.get_value(), a25.z.get_value()] + ) + xyz33a = numpy.array( + [a33.x.get_value(), a33.y.get_value(), a33.z.get_value()] + ) + + v12a = xyz0a - xyz7a + v23a = xyz7a - xyz20a + v34a = xyz20a - xyz25a + v123a = numpy.cross(v12a, v23a) + v234a = numpy.cross(v23a, v34a) + + d123a = numpy.dot(v123a, v123a) ** 0.5 + d234a = numpy.dot(v234a, v234a) ** 0.5 + angle1 = -numpy.arccos(numpy.dot(v123a, v234a) / (d123a * d234a)) + assert scale * angle0 == pytest.approx(angle1, abs=1e-6) + + # Verify that only the last two atoms have moved. + + assert numpy.array_equal(xyz0, xyz0a) + assert numpy.array_equal(xyz7, xyz7a) + assert numpy.array_equal(xyz20, xyz20a) + assert not numpy.array_equal(xyz25, xyz25a) + assert not numpy.array_equal(xyz33, xyz33a) + + return + + +class TestCreateSpaceGroup: + """Test space group creation from pyobjcryst structures. + + This makes sure that the space groups created by the structure + parameter set are correct. + """ + + @pytest.fixture(autouse=True) + def setup(self, diffpy_structure_available, pyobjcryst_available): + # shared setup + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + if not pyobjcryst_available: + pytest.skip("pyobjcryst package not available") + + global ObjCrystCrystalParSet, spacegroups + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + from diffpy.structure import spacegroups + + @staticmethod + def getObjCrystParSetSpaceGroup(sg): + """Make an ObjCrystCrystalParSet with the proper space group.""" + from pyobjcryst.spacegroup import SpaceGroup + + sgobjcryst = SpaceGroup(sg.short_name) + sgnew = ObjCrystCrystalParSet._create_space_group(sgobjcryst) + return sgnew + + @staticmethod + def hashDiffPySpaceGroup(sg): + lines = [str(sg.number % 1000)] + sorted(map(str, sg.iter_symops())) + s = "\n".join(lines) + return s + + def sgsEquivalent(self, sg1, sg2): + """Check to see if two space group objects are the same.""" + hash1 = self.hashDiffPySpaceGroup(sg1) + hash2 = self.hashDiffPySpaceGroup(sg2) + return hash1 == hash2 + + # FIXME: only about 50% of the spacegroups pass the assertion + # test disabled even if cctbx is installed + def xtestCreateSpaceGroup(self): + """Check all sgtbx space groups for proper conversion to + SpaceGroup.""" + try: + from cctbx import sgtbx + except ImportError: + return + + for smbls in sgtbx.space_group_symbol_iterator(): + shn = smbls.hermann_mauguin() + short_name = shn.replace(" ", "") + if spacegroups.IsSpaceGroupIdentifier(short_name): + sg = spacegroups.GetSpaceGroup(shn) + sgnew = self.getObjCrystParSetSpaceGroup(sg) + # print("dbsg: " + repr(self.sgsEquivalent(sg, sgnew))) + assert self.sgsEquivalent(sg, sgnew) + return + + +# End of class TestCreateSpaceGroup + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py new file mode 100644 index 0000000..56498ff --- /dev/null +++ b/tests/test_sgconstraints.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Tests space group constraints.""" + +import re +import unittest + +import numpy +import pytest + +# ---------------------------------------------------------------------------- + + +def test_ObjCryst_constrain_space_group(pyobjcryst_available): + """Make sure that all Parameters are constrained properly. + + This tests constrainSpaceGroup from + diffpy.srfit.structure.sgconstraints, which is performed + automatically when an ObjCrystCrystalParSet is created. + """ + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + + pi = numpy.pi + + occryst = makeLaMnO3() + stru = ObjCrystCrystalParSet(occryst.GetName(), occryst) + # Make sure we actually create the constraints + stru._constrain_space_group() + # Make the space group parameters individually + stru.sgpars.latpars + stru.sgpars.xyzpars + stru.sgpars.adppars + + # Check the orthorhombic lattice + lattice = stru.getLattice() + assert lattice.alpha.const + assert lattice.beta.const + assert lattice.gamma.const + assert pi / 2 == lattice.alpha.get_value() + assert pi / 2 == lattice.beta.get_value() + assert pi / 2 == lattice.gamma.get_value() + + assert not lattice.a.const + assert not lattice.b.const + assert not lattice.c.const + assert 0 == len(lattice._constraints) + + # Now make sure the scatterers are constrained properly + scatterers = stru.getScatterers() + la = scatterers[0] + assert not la.x.const + assert not la.y.const + assert la.z.const + assert 0 == len(la._constraints) + + mn = scatterers[1] + assert mn.x.const + assert mn.y.const + assert mn.z.const + assert 0 == len(mn._constraints) + + o1 = scatterers[2] + assert not o1.x.const + assert not o1.y.const + assert o1.z.const + assert 0 == len(o1._constraints) + + o2 = scatterers[3] + assert not o2.x.const + assert not o2.y.const + assert not o2.z.const + assert 0 == len(o2._constraints) + + # Make sure we can't constrain these + with pytest.raises(ValueError): + mn.add_constraint(mn.x, "y") + + with pytest.raises(ValueError): + mn.add_constraint(mn.y, "z") + + with pytest.raises(ValueError): + mn.add_constraint(mn.z, "x") + + # Nor can we make them into variables + from diffpy.srfit.fitbase.fitrecipe import FitRecipe + + f = FitRecipe() + with pytest.raises(ValueError): + f.add_variable(mn.x) + + return + + +def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): + """Test the constrain_as_space_group function.""" + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.srfit.structure.sgconstraints import constrain_as_space_group + + stru = makeLaMnO3_P1(datafile) + parset = DiffpyStructureParSet("LaMnO3", stru) + + sgpars = constrain_as_space_group( + parset, + "P b n m", + scatterers=parset.getScatterers()[::2], + constrainadps=True, + ) + + # Make sure that the new parameters were created + for par in sgpars: + assert par is not None + assert par.get_value() is not None + + # Test the unconstrained atoms + for scatterer in parset.getScatterers()[1::2]: + assert not scatterer.x.const + assert not scatterer.y.const + assert not scatterer.z.const + assert not scatterer.U11.const + assert not scatterer.U22.const + assert not scatterer.U33.const + assert not scatterer.U12.const + assert not scatterer.U13.const + assert not scatterer.U23.const + assert 0 == len(scatterer._constraints) + + proxied = [p.par for p in sgpars] + + def _consttest(par): + return par.const + + def _constrainedtest(par): + return par.constrained + + def _proxytest(par): + return par in proxied + + def _alltests(par): + return _consttest(par) or _constrainedtest(par) or _proxytest(par) + + for idx, scatterer in enumerate(parset.getScatterers()[::2]): + # Under this scheme, atom 6 is free to vary + test = False + for par in [scatterer.x, scatterer.y, scatterer.z]: + test |= _alltests(par) + assert test + + test = False + for par in [ + scatterer.U11, + scatterer.U22, + scatterer.U33, + scatterer.U12, + scatterer.U13, + scatterer.U23, + ]: + test |= _alltests(par) + + assert test + + return + + +def test_constrain_as_space_group_args(pyobjcryst_available, datafile): + """Test the arguments processing of constrain_as_space_group + function.""" + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.srfit.structure.sgconstraints import constrain_as_space_group + from diffpy.structure.spacegroups import GetSpaceGroup + + stru = makeLaMnO3_P1(datafile) + parset = DiffpyStructureParSet("LaMnO3", stru) + sgpars = constrain_as_space_group(parset, "P b n m") + sg = GetSpaceGroup("P b n m") + parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) + sgpars2 = constrain_as_space_group(parset2, sg) + list(sgpars) + list(sgpars2) + assert sgpars.names == sgpars2.names + return + + +# ---------------------------------------------------------------------------- +# constrainAsSpaceGroup is deprecated in favor of constrain_as_space_group. +# The old name must still work, emit a DeprecationWarning naming its +# replacement, and forward to the new implementation. + + +def test_constrainAsSpaceGroup_warns_and_forwards( + pyobjcryst_available, datafile +): + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.srfit.structure.sgconstraints import ( + constrain_as_space_group, + constrainAsSpaceGroup, + ) + + module_path = "diffpy.srfit.structure.sgconstraints" + expected_msg = ( + f"'{module_path}.constrainAsSpaceGroup' is deprecated and will be " + f"removed in version 4.0.0. Please use " + f"'{module_path}.constrain_as_space_group' instead." + ) + + parset = DiffpyStructureParSet("LaMnO3", makeLaMnO3_P1(datafile)) + with pytest.warns(DeprecationWarning, match=re.escape(expected_msg)): + actual_sgpars = constrainAsSpaceGroup(parset, "P b n m") + + parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) + expected_sgpars = constrain_as_space_group(parset2, "P b n m") + + assert actual_sgpars.names == expected_sgpars.names + return + + +def makeLaMnO3_P1(datafile): + from diffpy.structure import Structure + + stru = Structure() + stru.read(datafile("LaMnO3.stru")) + return stru + + +def makeLaMnO3(): + from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal + from pyobjcryst.scatteringpower import ScatteringPowerAtom + + pi = numpy.pi + # It appears that ObjCryst only supports standard symbols + crystal = Crystal(5.486341, 5.619215, 7.628206, "P b n m") + crystal.SetName("LaMnO3") + # La1 + sp = ScatteringPowerAtom("La1", "La") + sp.SetBiso(8 * pi * pi * 0.003) + atom = Atom(0.996096, 0.0321494, 0.25, "La1", sp) + crystal.AddScatteringPower(sp) + crystal.AddScatterer(atom) + # Mn1 + sp = ScatteringPowerAtom("Mn1", "Mn") + sp.SetBiso(8 * pi * pi * 0.003) + atom = Atom(0, 0.5, 0, "Mn1", sp) + crystal.AddScatteringPower(sp) + crystal.AddScatterer(atom) + # O1 + sp = ScatteringPowerAtom("O1", "O") + sp.SetBiso(8 * pi * pi * 0.003) + atom = Atom(0.0595746, 0.496164, 0.25, "O1", sp) + crystal.AddScatteringPower(sp) + crystal.AddScatterer(atom) + # O2 + sp = ScatteringPowerAtom("O2", "O") + sp.SetBiso(8 * pi * pi * 0.003) + atom = Atom(0.720052, 0.289387, 0.0311126, "O2", sp) + crystal.AddScatteringPower(sp) + crystal.AddScatterer(atom) + + return crystal + + +# ---------------------------------------------------------------------------- + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testdata/LaMnO3.stru b/tests/testdata/LaMnO3.stru new file mode 100644 index 0000000..044869a --- /dev/null +++ b/tests/testdata/LaMnO3.stru @@ -0,0 +1,129 @@ +title Cell structure file of LaMnO3.0 +format pdffit +scale 1.000000 +sharp 0.000000, 0.000000, 1.000000, 3.500000 +spcgr Pbnm +cell 5.486341, 5.619215, 7.628206, 90.000000, 90.000000, 90.000000 +dcell 0.000118, 0.000156, 0.000118, 0.000000, 0.000000, 0.000000 +ncell 1, 1, 1, 20 +atoms +LA 0.99609631 0.03214940 0.25000000 1.0000 + 0.00003041 0.00000852 0.00000000 0.0000 + 0.00253993 0.00253993 0.00253993 + 0.00000214 0.00000214 0.00000214 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +LA 0.49609631 0.46785060 0.75000000 1.0000 + 0.00003041 0.00000852 0.00000000 0.0000 + 0.00253993 0.00253993 0.00253993 + 0.00000214 0.00000214 0.00000214 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +LA 0.00390369 0.96785063 0.75000000 1.0000 + 0.00003041 0.00000852 0.00000000 0.0000 + 0.00253993 0.00253993 0.00253993 + 0.00000214 0.00000214 0.00000214 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +LA 0.50390369 0.53214937 0.25000000 1.0000 + 0.00003041 0.00000852 0.00000000 0.0000 + 0.00253993 0.00253993 0.00253993 + 0.00000214 0.00000214 0.00000214 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +MN 0.00000000 0.50000000 0.00000000 1.0000 + 0.00000000 0.00000000 0.00000000 0.0000 + 0.00065337 0.00065337 0.00065337 + 0.00000165 0.00000165 0.00000165 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +MN 0.50000000 0.00000000 0.00000000 1.0000 + 0.00000000 0.00000000 0.00000000 0.0000 + 0.00065337 0.00065337 0.00065337 + 0.00000165 0.00000165 0.00000165 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +MN 0.00000000 0.50000000 0.50000000 1.0000 + 0.00000000 0.00000000 0.00000000 0.0000 + 0.00065337 0.00065337 0.00065337 + 0.00000165 0.00000165 0.00000165 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +MN 0.50000000 0.00000000 0.50000000 1.0000 + 0.00000000 0.00000000 0.00000000 0.0000 + 0.00065337 0.00065337 0.00065337 + 0.00000165 0.00000165 0.00000165 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.05957463 0.49616399 0.25000000 1.0000 + 0.00001546 0.00001610 0.00000000 0.0000 + 0.00082010 0.00082010 0.00082010 + 0.00000137 0.00000137 0.00000137 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.55957460 0.00383601 0.75000000 1.0000 + 0.00001546 0.00001610 0.00000000 0.0000 + 0.00082010 0.00082010 0.00082010 + 0.00000137 0.00000137 0.00000137 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.94042540 0.50383604 0.75000000 1.0000 + 0.00001546 0.00001610 0.00000000 0.0000 + 0.00082010 0.00082010 0.00082010 + 0.00000137 0.00000137 0.00000137 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.44042537 0.99616396 0.25000000 1.0000 + 0.00001546 0.00001610 0.00000000 0.0000 + 0.00082010 0.00082010 0.00082010 + 0.00000137 0.00000137 0.00000137 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.72005206 0.28938726 0.03111255 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.22005206 0.21061274 0.96888745 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.27994794 0.71061277 0.53111255 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.77994794 0.78938723 0.46888745 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.27994794 0.71061277 0.96888745 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.77994794 0.78938723 0.03111255 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.72005206 0.28938726 0.46888745 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 +O 0.22005206 0.21061274 0.53111255 1.0000 + 0.00001528 0.00001560 0.00002506 0.0000 + 0.00512371 0.00512371 0.00512371 + 0.00000153 0.00000153 0.00000153 + 0.00000000 0.00000000 0.00000000 + 0.00000000 0.00000000 0.00000000 From c4c83e74ea7258d8e596c64e0539d32b6cd90cfe Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:15:35 -0400 Subject: [PATCH 02/13] fix: repoint structure imports at diffpy.cmistructure Replace diffpy.srfit.structure imports and references with diffpy.cmistructure in the copied modules and tests, and make the package importable and testable: - __init__.py: restore the package header and __version__ import. - src/diffpy/__init__.py: add extend_path so diffpy.cmistructure does not shadow the rest of the diffpy namespace. - pyproject.toml: point the entry point at cmistructure_app:main. - requirements: declare diffpy.srfit, diffpy.structure, diffpy.srreal, and pyobjcryst for tests. - tests: add datafile and availability fixtures and tests/__init__.py; remove the template functions.py and its test. Co-Authored-By: Claude Opus 5.5 --- news/migrate-structure.rst | 23 ++++++++++ pyproject.toml | 2 +- requirements/conda.txt | 3 ++ requirements/pip.txt | 3 ++ requirements/tests.txt | 1 + src/diffpy/__init__.py | 3 ++ src/diffpy/cmistructure/__init__.py | 39 +++++++++------- src/diffpy/cmistructure/cctbxparset.py | 4 +- src/diffpy/cmistructure/diffpyparset.py | 2 +- src/diffpy/cmistructure/functions.py | 31 ------------- src/diffpy/cmistructure/objcrystparset.py | 20 ++++----- src/diffpy/cmistructure/sgconstraints.py | 4 +- src/diffpy/cmistructure/srrealparset.py | 4 +- tests/__init__.py | 0 tests/conftest.py | 54 +++++++++++++++++++++++ tests/test_diffpyparset.py | 4 +- tests/test_functions.py | 40 ----------------- tests/test_objcrystparset.py | 6 +-- tests/test_sgconstraints.py | 20 ++++----- 19 files changed, 142 insertions(+), 121 deletions(-) create mode 100644 news/migrate-structure.rst delete mode 100644 src/diffpy/cmistructure/functions.py create mode 100644 tests/__init__.py delete mode 100644 tests/test_functions.py diff --git a/news/migrate-structure.rst b/news/migrate-structure.rst new file mode 100644 index 0000000..1f69489 --- /dev/null +++ b/news/migrate-structure.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add the structure adapters and space group constraints migrated from ``diffpy.srfit.structure``. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Fix the ``diffpy-cmistructure`` entry point to point at ``cmistructure_app``. + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 8fd77f0..bc2621a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,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-cmistructure = "diffpy.cmistructure.app:main" +diffpy-cmistructure = "diffpy.cmistructure.cmistructure_app:main" [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} diff --git a/requirements/conda.txt b/requirements/conda.txt index 24ce15a..6b1daa8 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1 +1,4 @@ numpy +diffpy.srreal +diffpy.srfit +diffpy.structure diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15a..6b1daa8 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,4 @@ numpy +diffpy.srreal +diffpy.srfit +diffpy.structure diff --git a/requirements/tests.txt b/requirements/tests.txt index a727786..d888d99 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -4,3 +4,4 @@ codecov coverage pytest-cov pytest-env +pyobjcryst diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index bfbc26c..e4af1d0 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -12,3 +12,6 @@ # See LICENSE.rst for license information. # ############################################################################## +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/src/diffpy/cmistructure/__init__.py b/src/diffpy/cmistructure/__init__.py index 0990c76..918ddac 100644 --- a/src/diffpy/cmistructure/__init__.py +++ b/src/diffpy/cmistructure/__init__.py @@ -1,26 +1,35 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## """Modules and classes that adapt structure representations to the ParameterSet interface and automatic structure constraint generation from space group information.""" -from diffpy.srfit.structure.sgconstraints import ( +from diffpy.cmistructure.sgconstraints import ( constrain_as_space_group, constrainAsSpaceGroup, ) +# package version +from diffpy.cmistructure.version import __version__ # noqa + +__all__ = [ + "constrain_as_space_group", + "constrainAsSpaceGroup", + "struToParameterSet", +] + def struToParameterSet(name, stru): """Creates a ParameterSet from an structure. @@ -37,22 +46,22 @@ def struToParameterSet(name, stru): Raises TypeError if stru cannot be adapted """ - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet if DiffpyStructureParSet.canAdapt(stru): return DiffpyStructureParSet(name, stru) - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet if ObjCrystCrystalParSet.canAdapt(stru): return ObjCrystCrystalParSet(name, stru) - from diffpy.srfit.structure.objcrystparset import ObjCrystMoleculeParSet + from diffpy.cmistructure.objcrystparset import ObjCrystMoleculeParSet if ObjCrystMoleculeParSet.canAdapt(stru): return ObjCrystMoleculeParSet(name, stru) - from diffpy.srfit.structure.cctbxparset import CCTBXCrystalParSet + from diffpy.cmistructure.cctbxparset import CCTBXCrystalParSet if CCTBXCrystalParSet.canAdapt(stru): return CCTBXCrystalParSet(name, stru) @@ -60,9 +69,7 @@ def struToParameterSet(name, stru): raise TypeError("Unadaptable structure format") -# silence pyflakes checker -assert constrain_as_space_group -assert constrainAsSpaceGroup - +# silence the pyflakes syntax checker +assert __version__ or True # End of file diff --git a/src/diffpy/cmistructure/cctbxparset.py b/src/diffpy/cmistructure/cctbxparset.py index d2c8902..9589440 100644 --- a/src/diffpy/cmistructure/cctbxparset.py +++ b/src/diffpy/cmistructure/cctbxparset.py @@ -27,9 +27,9 @@ - `CCTBXScattererParSet`: wrapper for `cctbx.xray.scatterer`. """ +from diffpy.cmistructure.basestructureparset import BaseStructureParSet from diffpy.srfit.fitbase.parameter import ParameterAdapter from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.srfit.structure.basestructureparset import BaseStructureParSet __all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", "CCTBXCrystalParSet"] @@ -253,7 +253,7 @@ def __init__(self, name, stru): self.scatterers.append(scatterer) # Constrain the lattice - from diffpy.srfit.structure.sgconstraints import _constrain_space_group + from diffpy.cmistructure.sgconstraints import _constrain_space_group symbol = self.getSpaceGroup() _constrain_space_group(self, symbol) diff --git a/src/diffpy/cmistructure/diffpyparset.py b/src/diffpy/cmistructure/diffpyparset.py index 6d2d17b..db800a4 100644 --- a/src/diffpy/cmistructure/diffpyparset.py +++ b/src/diffpy/cmistructure/diffpyparset.py @@ -31,9 +31,9 @@ __all__ = ["DiffpyStructureParSet"] +from diffpy.cmistructure.srrealparset import SrRealParSet from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.srfit.structure.srrealparset import SrRealParSet from diffpy.srfit.util.argbinders import bind2nd diff --git a/src/diffpy/cmistructure/functions.py b/src/diffpy/cmistructure/functions.py deleted file mode 100644 index e7e2c8e..0000000 --- a/src/diffpy/cmistructure/functions.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np - - -def dot_product(a, b): - """Compute the dot product of two vectors of any size. - - Ensure that the inputs, a and b, are of the same size. - The supported types are "array_like" objects, which can - be converted to a NumPy array. Examples include lists and tuples. - - Parameters - ---------- - a : array_like - The first input vector. - b : array_like - The second input vector. - - Returns - ------- - float - The dot product of the two vectors. - - Examples - -------- - Compute the dot product of two lists: - >>> a = [1, 2, 3] - >>> b = [4, 5, 6] - >>> dot_product(a, b) - 32.0 - """ - return float(np.dot(a, b)) diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index 3ccf993..a50cf7e 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -51,17 +51,17 @@ StretchModeTorsion, ) +from diffpy.cmistructure.srrealparset import SrRealParSet from diffpy.srfit.fitbase.parameter import ( Parameter, ParameterAdapter, ParameterProxy, ) from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.srfit.structure.srrealparset import SrRealParSet from diffpy.utils._deprecator import build_deprecation_message, deprecated removal_version = "4.0.0" -bl_base = "diffpy.srfit.structure.objcrystparset.ObjCrystBondLengthParameter" +bl_base = "diffpy.cmistructure.objcrystparset.ObjCrystBondLengthParameter" bl_setConst_dep_msg = build_deprecation_message( bl_base, @@ -70,7 +70,7 @@ removal_version, ) -ba_base = "diffpy.srfit.structure.objcrystparset.ObjCrystBondAngleParameter" +ba_base = "diffpy.cmistructure.objcrystparset.ObjCrystBondAngleParameter" ba_setConst_dep_msg = build_deprecation_message( ba_base, @@ -79,9 +79,7 @@ removal_version, ) -da_base = ( - "diffpy.srfit.structure.objcrystparset.ObjCrystDihedralAngleParameter" -) +da_base = "diffpy.cmistructure.objcrystparset.ObjCrystDihedralAngleParameter" da_setConst_dep_msg = build_deprecation_message( da_base, @@ -1400,7 +1398,7 @@ def setConst(self, const=True, value=None): 4.0.0. Please use - diffpy.srfit.structure.objcryst.ObjCrystBondLengthParameter.set_constant + diffpy.cmistructure.objcryst.ObjCrystBondLengthParameter.set_constant instead. """ self.set_constant(const, value) @@ -1561,7 +1559,7 @@ def setConst(self, const=True, value=None): version 4.0.0. Please use - diffpy.srfit.structure.objcryst.ObjCrystBondAngleParameter.set_constant + diffpy.cmistructure.objcryst.ObjCrystBondAngleParameter.set_constant instead. """ self.set_constant(const, value) @@ -1741,7 +1739,7 @@ def setConst(self, const=True, value=None): version 4.0.0. Please use - diffpy.srfit.structure.objcryst.ObjCrystDihedralAngleParameter.set_constant + diffpy.cmistructure.objcryst.ObjCrystDihedralAngleParameter.set_constant instead. """ self.set_constant(const, value) @@ -1785,7 +1783,7 @@ class ObjCrystCrystalParSet(SrRealParSet): convenience. _sgpars A BaseSpaceGroupParameters object containing free structure - Parameters. See the diffpy.srfit.structure.sgconstraints + Parameters. See the diffpy.cmistructure.sgconstraints module. sgpars property that creates _sgpars when it is needed. @@ -1859,7 +1857,7 @@ def _constrain_space_group(self): if self._sgpars is not None: return self._sgpars sg = self._create_space_group(self.stru.GetSpaceGroup()) - from diffpy.srfit.structure.sgconstraints import ( + from diffpy.cmistructure.sgconstraints import ( _constrain_as_space_group, ) diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index 22d57c8..1826fb9 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -25,7 +25,7 @@ __all__ = ["constrain_as_space_group", "constrainAsSpaceGroup"] removal_version = "4.0.0" -sgconstraints_base = "diffpy.srfit.structure.sgconstraints" +sgconstraints_base = "diffpy.cmistructure.sgconstraints" constrainAsSpaceGroup_dep_msg = build_deprecation_message( sgconstraints_base, @@ -146,7 +146,7 @@ def constrainAsSpaceGroup( 4.0.0. Please use - diffpy.srfit.structure.sgconstraints.constrain_as_space_group + diffpy.cmistructure.sgconstraints.constrain_as_space_group instead. """ return constrain_as_space_group( diff --git a/src/diffpy/cmistructure/srrealparset.py b/src/diffpy/cmistructure/srrealparset.py index e98f06c..61c5538 100644 --- a/src/diffpy/cmistructure/srrealparset.py +++ b/src/diffpy/cmistructure/srrealparset.py @@ -16,8 +16,8 @@ __all__ = ["SrRealParSet"] -from diffpy.srfit.structure.basestructureparset import BaseStructureParSet -from diffpy.srfit.structure.bvsrestraint import BVSRestraint +from diffpy.cmistructure.basestructureparset import BaseStructureParSet +from diffpy.cmistructure.bvsrestraint import BVSRestraint class SrRealParSet(BaseStructureParSet): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index e3b6313..b3398ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,13 @@ +import importlib.resources import json +import logging +from functools import lru_cache from pathlib import Path import pytest +logger = logging.getLogger(__name__) + @pytest.fixture def user_filesystem(tmp_path): @@ -17,3 +22,52 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path + + +# diffpy.structure +@lru_cache() +def has_diffpy_structure(): + try: + import diffpy.structure as m + + del m + return True + except ImportError: + logger.warning( + "Cannot import diffpy.structure, Structure tests skipped." + ) + return False + + +# pyobjcryst +@lru_cache() +def has_pyobjcryst(): + try: + import pyobjcryst as m + + del m + return True + except ImportError: + logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") + return False + + +@pytest.fixture(scope="session") +def diffpy_structure_available(): + return has_diffpy_structure() + + +@pytest.fixture(scope="session") +def pyobjcryst_available(): + return has_pyobjcryst() + + +@pytest.fixture(scope="session") +def datafile(): + """Fixture to load a test data file from the testdata package + directory.""" + + def _datafile(filename): + return importlib.resources.files("tests.testdata").joinpath(filename) + + return _datafile diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index cecda1c..e582175 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -12,14 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## -"""Tests for diffpy.srfit.structure package.""" +"""Tests for diffpy.cmistructure package.""" import pickle import unittest import numpy as np -from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet +from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet def testDiffpyStructureParSet(): diff --git a/tests/test_functions.py b/tests/test_functions.py deleted file mode 100644 index 1ec72dd..0000000 --- a/tests/test_functions.py +++ /dev/null @@ -1,40 +0,0 @@ -import numpy as np -import pytest - -from diffpy.cmistructure import functions # noqa - - -def test_dot_product_2D_list(): - a = [1, 2] - b = [3, 4] - expected = 11.0 - actual = functions.dot_product(a, b) - assert actual == expected - - -def test_dot_product_3D_list(): - a = [1, 2, 3] - b = [4, 5, 6] - expected = 32.0 - actual = functions.dot_product(a, b) - assert actual == expected - - -@pytest.mark.parametrize( - "a, b, expected", - [ - # Test whether the dot product function works with 2D and 3D vectors - # C1: lists, expect correct float output - ([1, 2], [3, 4], 11.0), - ([1, 2, 3], [4, 5, 6], 32.0), - # C2: tuples, expect correct float output - ((1, 2), (3, 4), 11.0), - ((1, 2, 3), (4, 5, 6), 32.0), - # C3: numpy arrays, expect correct float output - (np.array([1, 2]), np.array([3, 4]), 11.0), - (np.array([1, 2, 3]), np.array([4, 5, 6]), 32.0), - ], -) -def test_dot_product(a, b, expected): - actual = functions.dot_product(a, b) - assert actual == expected diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index 1edc95b..f9b409f 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -12,7 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## -"""Tests for diffpy.srfit.structure package.""" +"""Tests for diffpy.cmistructure package.""" import unittest @@ -125,7 +125,7 @@ def setup(self, pyobjcryst_available): from pyobjcryst.molecule import Molecule from pyobjcryst.scatteringpower import ScatteringPowerAtom - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet self.occryst = makeC60() self.ocmol = self.occryst.GetScatterer("c60") @@ -634,7 +634,7 @@ def setup(self, diffpy_structure_available, pyobjcryst_available): pytest.skip("pyobjcryst package not available") global ObjCrystCrystalParSet, spacegroups - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet from diffpy.structure import spacegroups @staticmethod diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 56498ff..d993d2f 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -27,13 +27,13 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): """Make sure that all Parameters are constrained properly. This tests constrainSpaceGroup from - diffpy.srfit.structure.sgconstraints, which is performed - automatically when an ObjCrystCrystalParSet is created. + diffpy.cmistructure.sgconstraints, which is performed automatically + when an ObjCrystCrystalParSet is created. """ if not pyobjcryst_available: pytest.skip("pyobjcrysta package not available") - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet pi = numpy.pi @@ -111,8 +111,8 @@ def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): if not pyobjcryst_available: pytest.skip("pyobjcrysta package not available") - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.srfit.structure.sgconstraints import constrain_as_space_group + from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet + from diffpy.cmistructure.sgconstraints import constrain_as_space_group stru = makeLaMnO3_P1(datafile) parset = DiffpyStructureParSet("LaMnO3", stru) @@ -185,8 +185,8 @@ def test_constrain_as_space_group_args(pyobjcryst_available, datafile): if not pyobjcryst_available: pytest.skip("pyobjcrysta package not available") - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.srfit.structure.sgconstraints import constrain_as_space_group + from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet + from diffpy.cmistructure.sgconstraints import constrain_as_space_group from diffpy.structure.spacegroups import GetSpaceGroup stru = makeLaMnO3_P1(datafile) @@ -213,13 +213,13 @@ def test_constrainAsSpaceGroup_warns_and_forwards( if not pyobjcryst_available: pytest.skip("pyobjcrysta package not available") - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.srfit.structure.sgconstraints import ( + from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet + from diffpy.cmistructure.sgconstraints import ( constrain_as_space_group, constrainAsSpaceGroup, ) - module_path = "diffpy.srfit.structure.sgconstraints" + module_path = "diffpy.cmistructure.sgconstraints" expected_msg = ( f"'{module_path}.constrainAsSpaceGroup' is deprecated and will be " f"removed in version 4.0.0. Please use " From ca2a52ffa8f060c1fa5514ef2ca66a7f2968b6a6 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:15:48 -0400 Subject: [PATCH 03/13] refactor: remove deprecation shims carried over from srfit diffpy.cmistructure has never been released, so it has no users to migrate. Drop constrainAsSpaceGroup, the three StretchModeParameter setConst forwarders in objcrystparset, their deprecation messages and diffpy.utils._deprecator imports, and the test for the constrainAsSpaceGroup warning. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/__init__.py | 11 +--- src/diffpy/cmistructure/objcrystparset.py | 65 ----------------------- src/diffpy/cmistructure/sgconstraints.py | 43 +-------------- tests/test_sgconstraints.py | 37 ------------- 4 files changed, 3 insertions(+), 153 deletions(-) diff --git a/src/diffpy/cmistructure/__init__.py b/src/diffpy/cmistructure/__init__.py index 918ddac..b144ac0 100644 --- a/src/diffpy/cmistructure/__init__.py +++ b/src/diffpy/cmistructure/__init__.py @@ -16,19 +16,12 @@ ParameterSet interface and automatic structure constraint generation from space group information.""" -from diffpy.cmistructure.sgconstraints import ( - constrain_as_space_group, - constrainAsSpaceGroup, -) +from diffpy.cmistructure.sgconstraints import constrain_as_space_group # package version from diffpy.cmistructure.version import __version__ # noqa -__all__ = [ - "constrain_as_space_group", - "constrainAsSpaceGroup", - "struToParameterSet", -] +__all__ = ["constrain_as_space_group", "struToParameterSet"] def struToParameterSet(name, stru): diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index a50cf7e..4d2bb78 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -58,35 +58,6 @@ ParameterProxy, ) from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.utils._deprecator import build_deprecation_message, deprecated - -removal_version = "4.0.0" -bl_base = "diffpy.cmistructure.objcrystparset.ObjCrystBondLengthParameter" - -bl_setConst_dep_msg = build_deprecation_message( - bl_base, - "setConst", - "set_constant", - removal_version, -) - -ba_base = "diffpy.cmistructure.objcrystparset.ObjCrystBondAngleParameter" - -ba_setConst_dep_msg = build_deprecation_message( - ba_base, - "setConst", - "set_constant", - removal_version, -) - -da_base = "diffpy.cmistructure.objcrystparset.ObjCrystDihedralAngleParameter" - -da_setConst_dep_msg = build_deprecation_message( - da_base, - "setConst", - "set_constant", - removal_version, -) class ObjCrystScattererParSet(ParameterSet): @@ -1392,18 +1363,6 @@ def set_constant(self, is_constant=True, value=None): a.z.set_constant(is_constant) return self - @deprecated(bl_setConst_dep_msg) - def setConst(self, const=True, value=None): - """This function has been deprecated and will be removed in version - 4.0.0. - - Please use - diffpy.cmistructure.objcryst.ObjCrystBondLengthParameter.set_constant - instead. - """ - self.set_constant(const, value) - return self - def get_value(self): """This calculates the value if it might have been changed. @@ -1553,18 +1512,6 @@ def set_constant(self, is_constant=True, value=None): a.z.set_constant(is_constant) return self - @deprecated(ba_setConst_dep_msg) - def setConst(self, const=True, value=None): - """This function has been deprecated and will be removed in - version 4.0.0. - - Please use - diffpy.cmistructure.objcryst.ObjCrystBondAngleParameter.set_constant - instead. - """ - self.set_constant(const, value) - return self - def get_value(self): """This calculates the value if it might have been changed. @@ -1733,18 +1680,6 @@ def set_constant(self, is_constant=True, value=None): a.z.set_constant(is_constant) return self - @deprecated(da_setConst_dep_msg) - def setConst(self, const=True, value=None): - """This function has been deprecated and will be removed in - version 4.0.0. - - Please use - diffpy.cmistructure.objcryst.ObjCrystDihedralAngleParameter.set_constant - instead. - """ - self.set_constant(const, value) - return self - def get_value(self): """This calculates the value if it might have been changed. diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index 1826fb9..ad4a890 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -20,19 +20,8 @@ from diffpy.srfit.fitbase.parameter import ParameterProxy from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer -from diffpy.utils._deprecator import build_deprecation_message, deprecated -__all__ = ["constrain_as_space_group", "constrainAsSpaceGroup"] - -removal_version = "4.0.0" -sgconstraints_base = "diffpy.cmistructure.sgconstraints" - -constrainAsSpaceGroup_dep_msg = build_deprecation_message( - sgconstraints_base, - "constrainAsSpaceGroup", - "constrain_as_space_group", - removal_version, -) +__all__ = ["constrain_as_space_group"] def constrain_as_space_group( @@ -131,36 +120,6 @@ def constrain_as_space_group( return sgp -@deprecated(constrainAsSpaceGroup_dep_msg) -def constrainAsSpaceGroup( - phase, - spacegroup, - scatterers=None, - sgoffset=[0, 0, 0], - constrainlat=True, - constrainadps=True, - adpsymbols=None, - isosymbol="Uiso", -): - """This function is deprecated and will be removed in version - 4.0.0. - - Please use - diffpy.cmistructure.sgconstraints.constrain_as_space_group - instead. - """ - return constrain_as_space_group( - phase, - spacegroup, - scatterers, - sgoffset, - constrainlat, - constrainadps, - adpsymbols, - isosymbol, - ) - - def _constrain_as_space_group( phase, sg, diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index d993d2f..3b6fd3f 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -14,7 +14,6 @@ ############################################################################## """Tests space group constraints.""" -import re import unittest import numpy @@ -201,42 +200,6 @@ def test_constrain_as_space_group_args(pyobjcryst_available, datafile): return -# ---------------------------------------------------------------------------- -# constrainAsSpaceGroup is deprecated in favor of constrain_as_space_group. -# The old name must still work, emit a DeprecationWarning naming its -# replacement, and forward to the new implementation. - - -def test_constrainAsSpaceGroup_warns_and_forwards( - pyobjcryst_available, datafile -): - if not pyobjcryst_available: - pytest.skip("pyobjcrysta package not available") - - from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet - from diffpy.cmistructure.sgconstraints import ( - constrain_as_space_group, - constrainAsSpaceGroup, - ) - - module_path = "diffpy.cmistructure.sgconstraints" - expected_msg = ( - f"'{module_path}.constrainAsSpaceGroup' is deprecated and will be " - f"removed in version 4.0.0. Please use " - f"'{module_path}.constrain_as_space_group' instead." - ) - - parset = DiffpyStructureParSet("LaMnO3", makeLaMnO3_P1(datafile)) - with pytest.warns(DeprecationWarning, match=re.escape(expected_msg)): - actual_sgpars = constrainAsSpaceGroup(parset, "P b n m") - - parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) - expected_sgpars = constrain_as_space_group(parset2, "P b n m") - - assert actual_sgpars.names == expected_sgpars.names - return - - def makeLaMnO3_P1(datafile): from diffpy.structure import Structure From 4a847fe404f110e00b58e8a6b98fd3b985998806 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:23:41 -0400 Subject: [PATCH 04/13] refactor: rename camelCase API to snake_case with NumPy docstrings Rename every camelCase function and method in the package to snake_case (e.g. canAdapt -> can_adapt, getScatterers -> get_scatterers, restrainBondLength -> restrain_bond_length, struToParameterSet -> stru_to_parameter_set), along with their call sites and tests. No deprecation aliases are kept since the package has not been released. Rewrite the public docstrings to the NumPy style: imperative one-line summary, typed Parameters/Returns/Raises sections with descriptions starting with "The", and the nonstandard "Managed Parameters" and "Inherited Attributes" sections folded into Attributes. Also replace ObjCrystMoleculeParSet.get_lattice's calls to newPar, which no longer exists on srfit's ParameterSet, with new_parameter. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/__init__.py | 39 +- .../cmistructure/basestructureparset.py | 53 +- src/diffpy/cmistructure/bvsrestraint.py | 33 +- src/diffpy/cmistructure/cctbxparset.py | 137 +- src/diffpy/cmistructure/diffpyparset.py | 177 +- src/diffpy/cmistructure/objcrystparset.py | 1526 +++++++++-------- src/diffpy/cmistructure/sgconstraints.py | 288 ++-- src/diffpy/cmistructure/srrealparset.py | 59 +- tests/test_objcrystparset.py | 30 +- tests/test_sgconstraints.py | 10 +- 10 files changed, 1245 insertions(+), 1107 deletions(-) diff --git a/src/diffpy/cmistructure/__init__.py b/src/diffpy/cmistructure/__init__.py index b144ac0..b3212f2 100644 --- a/src/diffpy/cmistructure/__init__.py +++ b/src/diffpy/cmistructure/__init__.py @@ -21,42 +21,51 @@ # package version from diffpy.cmistructure.version import __version__ # noqa -__all__ = ["constrain_as_space_group", "struToParameterSet"] +__all__ = ["constrain_as_space_group", "stru_to_parameter_set"] -def struToParameterSet(name, stru): - """Creates a ParameterSet from an structure. +def stru_to_parameter_set(name, stru): + """Create a ParameterSet adapted to a structure object. - This returns a ParameterSet adapted for the structure depending on its - type. + The adapter is chosen from the type of `stru`. Supported types are + diffpy.structure.Structure, pyobjcryst.crystal.Crystal, + pyobjcryst.molecule.Molecule and cctbx.crystal.special_position_settings. Parameters ---------- - stru - a structure object known by this module - name - A name to give the structure. - - Raises TypeError if stru cannot be adapted + name : str + The name to give the structure. + stru : object + The structure object to adapt. + + Returns + ------- + BaseStructureParSet + The ParameterSet adapting `stru`. + + Raises + ------ + TypeError + If `stru` is not one of the supported structure types. """ from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet - if DiffpyStructureParSet.canAdapt(stru): + if DiffpyStructureParSet.can_adapt(stru): return DiffpyStructureParSet(name, stru) from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet - if ObjCrystCrystalParSet.canAdapt(stru): + if ObjCrystCrystalParSet.can_adapt(stru): return ObjCrystCrystalParSet(name, stru) from diffpy.cmistructure.objcrystparset import ObjCrystMoleculeParSet - if ObjCrystMoleculeParSet.canAdapt(stru): + if ObjCrystMoleculeParSet.can_adapt(stru): return ObjCrystMoleculeParSet(name, stru) from diffpy.cmistructure.cctbxparset import CCTBXCrystalParSet - if CCTBXCrystalParSet.canAdapt(stru): + if CCTBXCrystalParSet.can_adapt(stru): return CCTBXCrystalParSet(name, stru) raise TypeError("Unadaptable structure format") diff --git a/src/diffpy/cmistructure/basestructureparset.py b/src/diffpy/cmistructure/basestructureparset.py index 4c97577..cb681b2 100644 --- a/src/diffpy/cmistructure/basestructureparset.py +++ b/src/diffpy/cmistructure/basestructureparset.py @@ -32,33 +32,66 @@ class BaseStructureParSet(ParameterSet): Attributes ---------- - stru - The adapted object + stru : object + The adapted structure object. """ @classmethod - def canAdapt(self, stru): - """Return whether the structure can be adapted by this class.""" + def can_adapt(self, stru): + """Return whether the structure can be adapted by this class. + + Parameters + ---------- + stru : object + The structure object to check. + + Returns + ------- + bool + The flag indicating if `stru` can be adapted. The base class + always returns False. + """ return False - def getLattice(self): - """Get a ParameterSet containing the lattice Parameters. + def get_lattice(self): + """Return the ParameterSet containing the lattice Parameters. The returned ParameterSet may contain other Parameters than the lattice Parameters. It is assumed that the lattice parameters are named "a", "b", "c", "alpha", "beta", "gamma". - Lattice must also have the "angunits" attribute, which is either - "deg" or "rad", to signify degrees or radians. + The lattice must also have the "angunits" attribute, which is + either "deg" or "rad", to signify degrees or radians. + + Returns + ------- + ParameterSet + The ParameterSet holding the lattice Parameters. + + Raises + ------ + NotImplementedError + If the subclass does not override this method. """ raise NotImplementedError("The must be overloaded") - def getScatterers(self): - """Get a list of ParameterSets that represents the scatterers. + def get_scatterers(self): + """Return the list of ParameterSets that represent the + scatterers. The site positions must be accessible from the list entries via the names "x", "y", and "z". The ADPs must be accessible as well, but the name and nature of the ADPs (U-factors, B-factors, isotropic, anisotropic) depends on the adapted structure. + + Returns + ------- + list of ParameterSet + The ParameterSets of the scatterers in the structure. + + Raises + ------ + NotImplementedError + If the subclass does not override this method. """ raise NotImplementedError("The must be overloaded") diff --git a/src/diffpy/cmistructure/bvsrestraint.py b/src/diffpy/cmistructure/bvsrestraint.py index ec15644..6ad1ca6 100644 --- a/src/diffpy/cmistructure/bvsrestraint.py +++ b/src/diffpy/cmistructure/bvsrestraint.py @@ -32,14 +32,14 @@ class BVSRestraint(Restraint): Attributes ---------- - _calc + _calc : BVSCalculator The SrReal BVSCalculator instance. - _parset + _parset : SrRealParSet The SrRealParSet that created this BVSRestraint. - sig + sig : float The uncertainty on the BVS (default 1). - scaled - A flag indicating if the restraint is scaled (multiplied) + scaled : bool + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ @@ -49,14 +49,14 @@ def __init__(self, parset, sig=1, scaled=False): Parameters ---------- - parset - SrRealParSet that creates this BVSRestraint. - sig + parset : SrRealParSet + The SrRealParSet that creates this BVSRestraint. + sig : float, optional The uncertainty on the BVS (default 1). - scaled - A flag indicating if the restraint is scaled - (multiplied) by the unrestrained point-average chi^2 - (chi^2/numpoints) (bool, default False). + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). """ from diffpy.srreal.bvscalculator import BVSCalculator @@ -71,9 +71,14 @@ def penalty(self, w=1.0): Parameters ---------- - w + w : float, optional The point-average chi^2 which is optionally used to scale the - penalty (float, default 1.0). + penalty (default 1.0). + + Returns + ------- + float + The bond-valence penalty. """ # Get the bvms from the BVSCalculator stru = self._parset._get_srreal_structure() diff --git a/src/diffpy/cmistructure/cctbxparset.py b/src/diffpy/cmistructure/cctbxparset.py index 9589440..9af3333 100644 --- a/src/diffpy/cmistructure/cctbxparset.py +++ b/src/diffpy/cmistructure/cctbxparset.py @@ -35,34 +35,34 @@ class CCTBXScattererParSet(ParameterSet): - """A wrapper for cctbx.xray.scatterer. + """Adapt a cctbx.xray.scatterer to the ParameterSet interface. This class derives from ParameterSet. Attributes ---------- - name - Name of the scatterer. The name is always of the form + name : str + The name of the scatterer. The name is always of the form "%s%i" % (element, number), where the number is the running index of that element type (starting at 0). - x (y, z) -- Atom position in crystal coordinates (ParameterAdapter) - occupancy - Occupancy of the atom on its crystal location - (ParameterAdapter) - Uiso - Isotropic scattering factor (ParameterAdapter). + x, y, z : ParameterAdapter + The atom position in crystal coordinates. + occupancy : ParameterAdapter + The occupancy of the atom on its crystal location. + Uiso : ParameterAdapter + The isotropic displacement factor of the atom. """ def __init__(self, name, strups, idx): - """Initialize. + """Initialize the scatterer ParameterSet. Parameters ---------- - name + name : str The name of this scatterer. - strups - The CCTBXCrystalParSet that contains the cctbx structure - idx + strups : CCTBXCrystalParSet + The CCTBXCrystalParSet that contains the cctbx structure. + idx : int The index of the scatterer in the structure. """ ParameterSet.__init__(self, name) @@ -130,34 +130,24 @@ def _getelem(self): class CCTBXUnitCellParSet(ParameterSet): - """A wrapper for cctbx unit_cell object. + """Adapt a cctbx unit_cell to the ParameterSet interface. Attributes ---------- - name - Always "unitcell". - a - Unit cell parameters (ParameterAdapter). - b - Unit cell parameters (ParameterAdapter). - c - Unit cell parameters (ParameterAdapter). - alpha - Unit cell parameters (ParameterAdapter). - beta - Unit cell parameters (ParameterAdapter). - gamma - Unit cell parameters (ParameterAdapter). + name : str + The name of this ParameterSet, always "unitcell". + a, b, c, alpha, beta, gamma : ParameterAdapter + The unit cell parameters. """ def __init__(self, strups): - """Initialize. + """Initialize the unit cell ParameterSet. Parameters ---------- - strups + strups : CCTBXCrystalParSet The CCTBXCrystalParSet that contains the cctbx structure - and the unit cell we're wrapper. + and the unit cell being wrapped. """ ParameterSet.__init__(self, "unitcell") self.strups = strups @@ -213,27 +203,27 @@ def f(dummy, value): class CCTBXCrystalParSet(BaseStructureParSet): - """A wrapper for CCTBX structure. + """Adapt a cctbx structure to the ParameterSet interface. Attributes ---------- - stru + stru : cctbx.crystal.special_position_settings The adapted cctbx structure object. - scatterers - The list of ScattererParSets. - unitcell - The CCTBXUnitCellParSet for the structure. + scatterers : list of CCTBXScattererParSet + The scatterer ParameterSets. + unitcell : CCTBXUnitCellParSet + The unit cell ParameterSet for the structure. """ def __init__(self, name, stru): - """Initialize. + """Initialize the crystal ParameterSet. Parameters ---------- - name - A name for this - stru - A CCTBX structure instance. + name : str + The name of this ParameterSet. + stru : cctbx.crystal.special_position_settings + The cctbx structure to adapt. """ ParameterSet.__init__(self, name) self.stru = stru @@ -255,18 +245,16 @@ def __init__(self, name, stru): # Constrain the lattice from diffpy.cmistructure.sgconstraints import _constrain_space_group - symbol = self.getSpaceGroup() + symbol = self.get_space_group() _constrain_space_group(self, symbol) return def update(self): - """Update the unit_cell to a change in lattice parameters. + """Rebuild the unit cell after a change in lattice parameters. - This remakes the unit cell according to a change in the lattice - parameters. Call this function before using the - CCTBXCrystalParSet. The unit_cell will only be remade if - necessary. + Call this function before using the CCTBXCrystalParSet. The unit + cell is only remade if a lattice parameter has changed. """ if not self._update: return @@ -293,30 +281,57 @@ def update(self): return @classmethod - def canAdapt(self, stru): - """Return whether the structure can be adapted by this class.""" + def can_adapt(self, stru): + """Return whether the structure can be adapted by this class. + + Parameters + ---------- + stru : object + The structure object to check. + + Returns + ------- + bool + The flag indicating if `stru` is a + cctbx.crystal.special_position_settings. False if cctbx is + not installed. + """ try: from cctbx.crystal import special_position_settings except ImportError: return False return isinstance(stru, special_position_settings) - def getLattice(self): - """Get the ParameterSet containing the lattice Parameters.""" + def get_lattice(self): + """Return the ParameterSet containing the lattice Parameters. + + Returns + ------- + CCTBXUnitCellParSet + The unit cell ParameterSet of the structure. + """ return self.unitcell - def getScatterers(self): - """Get a list of ParameterSets that represents the scatterers. + def get_scatterers(self): + """Return the list of ParameterSets that represent the + scatterers. - The site positions must be accessible from the list entries via - the names "x", "y", and "z". The ADPs must be accessible as - well, but the name and nature of the ADPs (U-factors, B-factors, - isotropic, anisotropic) depends on the adapted structure. + Returns + ------- + list of CCTBXScattererParSet + The scatterer ParameterSets of the structure. """ return self.scatterers - def getSpaceGroup(self): - """Get the HM space group symbol for the structure.""" + def get_space_group(self): + """Return the Hermann-Mauguin space group symbol of the + structure. + + Returns + ------- + str + The Hermann-Mauguin space group symbol. + """ sg = self.stru.space_group() t = sg.type() return t.lookup_symbol() diff --git a/src/diffpy/cmistructure/diffpyparset.py b/src/diffpy/cmistructure/diffpyparset.py index db800a4..73c2487 100644 --- a/src/diffpy/cmistructure/diffpyparset.py +++ b/src/diffpy/cmistructure/diffpyparset.py @@ -57,46 +57,46 @@ def __call__(self, atom, value): class DiffpyAtomParSet(ParameterSet): - """A wrapper for diffpy.structure.Atom. + """Adapt a diffpy.structure.Atom to the ParameterSet interface. - This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. See - this class for base attributes. + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. + See that class for base attributes. Attributes ---------- - atom - The diffpy.structure.Atom this is adapting - element + atom : diffpy.structure.Atom + The atom this is adapting. + element : str The element name (property). - - Managed Parameters - ------------------ - occupancy - Occupancy of the atom on its crystal location - (ParameterAdapter) - occ - Proxy for occupancy (ParameterProxy). - U11, U22, U33, U12, U21, U23, U32, U13, U31 - -- Anisotropic displacement factor for atom (ParameterAdapter - or ParameterProxy). Note that the Uij and Uji parameters - are the same. - Uiso - Isotropic ADP (ParameterAdapter). - B11, B22, B33, B12, B21, B23, B32, B13, B31 - -- Anisotropic displacement factor for atom (ParameterAdapter - or ParameterProxy). Note that the Bij and Bji parameters - are the same. (Bij = 8*pi**2*Uij) - Biso - Isotropic ADP (ParameterAdapter). + x, y, z : ParameterAdapter + The fractional coordinates of the atom. + occupancy : ParameterAdapter + The occupancy of the atom on its crystal location. + occ : ParameterProxy + The proxy for `occupancy`. + Uij : ParameterAdapter or ParameterProxy + The anisotropic displacement factors U11, U22, U33, U12, U21, U13, + U31, U23 and U32 of the atom. The Uij and Uji parameters are the + same. + Uiso : ParameterAdapter + The isotropic displacement factor of the atom. + Bij : ParameterAdapter or ParameterProxy + The anisotropic displacement factors B11, B22, B33, B12, B21, B13, + B31, B23 and B32 of the atom, with Bij = 8*pi**2*Uij. The Bij and + Bji parameters are the same. + Biso : ParameterAdapter + The isotropic displacement factor of the atom, as a B-factor. """ def __init__(self, name, atom): - """Initialize. + """Initialize the atom ParameterSet. Parameters ---------- - atom - A diffpy.structure.Atom instance + name : str + The name of this ParameterSet. + atom : diffpy.structure.Atom + The atom to adapt. """ ParameterSet.__init__(self, name) self.atom = atom @@ -174,43 +174,30 @@ def _latsetter(par): class DiffpyLatticeParSet(ParameterSet): - """A wrapper for diffpy.structure.Lattice. + """Adapt a diffpy.structure.Lattice to the ParameterSet interface. This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. - See this class for base attributes. + See that class for base attributes. Attributes ---------- - lattice - The diffpy.structure.Lattice this is adapting - name - Always "lattice" - angunits - "deg", the units of angle - - Parameters - ---------- - a - Unit cell parameters (ParameterAdapter). - b - Unit cell parameters (ParameterAdapter). - c - Unit cell parameters (ParameterAdapter). - alpha - Unit cell parameters (ParameterAdapter). - beta - Unit cell parameters (ParameterAdapter). - gamma - Unit cell parameters (ParameterAdapter). + lattice : diffpy.structure.Lattice + The lattice this is adapting. + name : str + The name of this ParameterSet, always "lattice". + angunits : str + The units of the lattice angles, always "deg". + a, b, c, alpha, beta, gamma : ParameterAdapter + The unit cell parameters. """ def __init__(self, lattice): - """Initialize. + """Initialize the lattice ParameterSet. Parameters ---------- - lattice - A diffpy.structure.Lattice instance + lattice : diffpy.structure.Lattice + The lattice to adapt. """ ParameterSet.__init__(self, "lattice") self.angunits = "deg" @@ -250,39 +237,35 @@ def __repr__(self): class DiffpyStructureParSet(SrRealParSet): - """A wrapper for diffpy.structure.Structure. + """Adapt a diffpy.structure.Structure to the ParameterSet interface. - This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. See - this class for base attributes. + This class derives from SrRealParSet. See that class for base + attributes. Attributes ---------- - atoms - The list of DiffpyAtomParSets, provided for convenience. - stru - The diffpy.structure.Structure this is adapting - - Managed ParameterSets - --------------------- - lattice - The managed DiffpyLatticeParSet - - A managed DiffpyAtomParSets. is the atomic element and - is the index of that element in the structure, - starting from zero. Thus, for nickel in P1 symmetry, the - managed DiffpyAtomParSets will be named "Ni0", "Ni1", "Ni2" - and "Ni3". + atoms : list of DiffpyAtomParSet + The atom ParameterSets, provided for convenience. + stru : diffpy.structure.Structure + The structure this is adapting. + lattice : DiffpyLatticeParSet + The managed lattice ParameterSet. + : DiffpyAtomParSet + The managed atom ParameterSets. is the atomic element and + is the index of that element in the structure, starting + from zero. For nickel in P1 symmetry, the managed + DiffpyAtomParSets are named "Ni0", "Ni1", "Ni2" and "Ni3". """ def __init__(self, name, stru): - """Initialize. + """Initialize the structure ParameterSet. Parameters ---------- - name - A name for the structure - stru - A diffpy.structure.Structure instance + name : str + The name of the structure. + stru : diffpy.structure.Structure + The structure to adapt. """ SrRealParSet.__init__(self, name) self.stru = stru @@ -307,24 +290,42 @@ def __init__(self, name, stru): def __repr__(self): return repr(self.stru) - def getLattice(self): - """Get the ParameterSet containing the lattice Parameters.""" + def get_lattice(self): + """Return the ParameterSet containing the lattice Parameters. + + Returns + ------- + DiffpyLatticeParSet + The lattice ParameterSet of the structure. + """ return self.lattice @classmethod - def canAdapt(self, stru): - """Return whether the structure can be adapted by this class.""" + def can_adapt(self, stru): + """Return whether the structure can be adapted by this class. + + Parameters + ---------- + stru : object + The structure object to check. + + Returns + ------- + bool + The flag indicating if `stru` is a diffpy.structure.Structure. + """ from diffpy.structure import Structure return isinstance(stru, Structure) - def getScatterers(self): - """Get a list of ParameterSets that represents the scatterers. + def get_scatterers(self): + """Return the list of ParameterSets that represent the + scatterers. - The site positions must be accessible from the list entries via - the names "x", "y", and "z". The ADPs must be accessible as - well, but the name and nature of the ADPs (U-factors, B-factors, - isotropic, anisotropic) depends on the adapted structure. + Returns + ------- + list of DiffpyAtomParSet + The atom ParameterSets of the structure. """ return self.atoms diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index 4d2bb78..5a19cfc 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -61,38 +61,36 @@ class ObjCrystScattererParSet(ParameterSet): - """A base adaptor for an Objcryst Scatterer. + """Base adapter for a pyobjcryst scatterer. - This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet and - adapts pyobjcryst.scatterer.Scatterer derivatives (Molecule, Atom) and - objects with a similar interface (MolAtom). See the ParameterSet class for - base attributes. + This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet + and adapts pyobjcryst.scatterer.Scatterer derivatives (Molecule, Atom) + and objects with a similar interface (MolAtom). See the ParameterSet + class for base attributes. Attributes ---------- - scat + scat : pyobjcryst.scatterer.Scatterer The adapted pyobjcryst object. - parent - The ParameterSet this belongs to - - Managed Parameters - ------------------ - occ - Occupancy of the scatterer on its crystal site - (ParameterWraper) + parent : ParameterSet or None + The ParameterSet this belongs to. + x, y, z : ParameterAdapter + The position of the scatterer in crystal coordinates. + occ : ParameterAdapter + The occupancy of the scatterer on its crystal site. """ def __init__(self, name, scat, parent): - """Initialize. + """Initialize the scatterer ParameterSet. Parameters ---------- - name - The name of the scatterer - scat - The pyobjcryst.Scatterer instance - parent - The ParameterSet this belongs to + name : str + The name of the scatterer. + scat : pyobjcryst.scatterer.Scatterer + The pyobjcryst scatterer to adapt. + parent : ParameterSet or None + The ParameterSet this belongs to. """ ParameterSet.__init__(self, name) self.scat = scat @@ -107,56 +105,66 @@ def __init__(self, name, scat, parent): ) return - def isDummy(self): - """Indicate whether this scatterer is a dummy atom.""" + def is_dummy(self): + """Return whether this scatterer is a dummy atom. + + Returns + ------- + bool + The flag indicating if this is a dummy atom. Always False for + this class. + """ return False - def hasScatterers(self): - """Indicate if this scatterer has its own scatterers.""" - return hasattr(self, "getScatterers") + def has_scatterers(self): + """Return whether this scatterer has its own scatterers. + + Returns + ------- + bool + The flag indicating if this scatterer has a ``get_scatterers`` + method. + """ + return hasattr(self, "get_scatterers") # End class ObjCrystScattererParSet class ObjCrystAtomParSet(ObjCrystScattererParSet): - """A adaptor for a pyobjcryst.Atom. + """Adapt a pyobjcryst.atom.Atom to the ParameterSet interface. This class derives from ObjCrystScattererParSet. Attributes ---------- - scat - The adapted pyobjcryst.atom.Atom. - element - Non-refinable name of the element (property). - parent - The ObjCrystCrystalParSet this belongs to. - - Managed Parameters - ------------------ - occ - Occupancy of the atom on its crystal location - (ParameterAdapter) - Biso - Isotropic scattering factor (ParameterAdapter). - B11, B22, B33, B12, B21, B23, B32, B13, B31 - -- Anisotropic displacement factor for scatterer - (ParameterAdapter or ParameterProxy). Note that the Bij and Bji - parameters are the same. + scat : pyobjcryst.atom.Atom + The adapted atom. + element : str + The non-refinable name of the element (property). + parent : ObjCrystCrystalParSet + The crystal ParameterSet this belongs to. + occ : ParameterAdapter + The occupancy of the atom on its crystal location. + Biso : ParameterAdapter + The isotropic displacement factor of the atom. + Bij : ParameterAdapter or ParameterProxy + The anisotropic displacement factors B11, B22, B33, B12, B21, B13, + B31, B23 and B32 of the atom. The Bij and Bji parameters are the + same. """ def __init__(self, name, atom, parent): - """Initialize. + """Initialize the atom ParameterSet. Parameters ---------- - name - The name of the scatterer - scat - The Scatterer instance - parent - The ObjCrystCrystalParSet this belongs to + name : str + The name of the atom. + atom : pyobjcryst.atom.Atom + The atom to adapt. + parent : ObjCrystCrystalParSet + The crystal ParameterSet this belongs to. """ ObjCrystScattererParSet.__init__(self, name, atom, parent) sp = atom.GetScatteringPower() @@ -195,44 +203,47 @@ def _getelem(self): class ObjCrystMoleculeParSet(ObjCrystScattererParSet): - """A adaptor for a pyobjcryst.Molecule. + """Adapt a pyobjcryst.molecule.Molecule to the ParameterSet + interface. - This class derives from ObjCrystScattererParSet. + This class derives from ObjCrystScattererParSet. Other attributes are + inherited from diffpy.srfit.fitbase.parameterset.ParameterSet. Attributes ---------- - scat - The adapted pyobjcryst.molecule.Molecule. - stru - The adapted pyobjcryst.molecule.Molecule. - parent - The ObjCrystCrystalParSet this belongs to. - ObjCrystMoleculeParSets can be used on their own, in which - case this is None. - - Managed Parameters - ------------------ - occ - Occupancy of the molecule on its crystal location - (ParameterAdapter) - q0, q1, q2, q3 -- Orientational quaternion (ParameterAdapter) - - - Other attributes are inherited from - diffpy.srfit.fitbase.parameterset.ParameterSet + scat : pyobjcryst.molecule.Molecule + The adapted molecule. + stru : pyobjcryst.molecule.Molecule + The adapted molecule. + parent : ObjCrystCrystalParSet or None + The crystal ParameterSet this belongs to. This is None when the + ObjCrystMoleculeParSet is used on its own. + atoms : list of ObjCrystMolAtomParSet + The ParameterSets of the atoms in the molecule. + occ : ParameterAdapter + The occupancy of the molecule on its crystal location. + q0, q1, q2, q3 : ParameterAdapter + The orientational quaternion of the molecule. """ def __init__(self, name, molecule, parent=None): - """Initialize. + """Initialize the molecule ParameterSet. Parameters ---------- - name - The name of the scatterer - molecule - The pyobjcryst.Molecule instance - parent - The ObjCrystCrystalParSet this belongs to (default None). + name : str + The name of the molecule. + molecule : pyobjcryst.molecule.Molecule + The molecule to adapt. + parent : ObjCrystCrystalParSet, optional + The crystal ParameterSet this belongs to (default None). + + Raises + ------ + AttributeError + If a MolAtom in the molecule has no name, or if two MolAtoms + share a name. Give every MolAtom a unique name before + wrapping the molecule. """ ObjCrystScattererParSet.__init__(self, name, molecule, parent) self.stru = molecule @@ -264,25 +275,46 @@ def __init__(self, name, molecule, parent=None): return @classmethod - def canAdapt(self, stru): - """Return whether the structure can be adapted by this class.""" + def can_adapt(self, stru): + """Return whether the structure can be adapted by this class. + + Parameters + ---------- + stru : object + The structure object to check. + + Returns + ------- + bool + The flag indicating if `stru` is a pyobjcryst Molecule. + """ from pyobjcryst.molecule import Molecule return isinstance(stru, Molecule) # Part of SrRealParSet interface - def useSymmetry(self, use=True): - """Set this structure to use symmetry. + def use_symmetry(self, use=True): + """Set whether this structure uses symmetry. - This structure object does not support symmetry. + This structure object does not support symmetry, so this does + nothing. + + Parameters + ---------- + use : bool, optional + The flag indicating if symmetry is used (default True). """ return # Part of SrRealParSet interface - def usingSymmetry(self): - """Check if symmetry is being used. + def using_symmetry(self): + """Return whether symmetry is being used. - This structure object does not support symmetry. + Returns + ------- + bool + The flag indicating if symmetry is used. Always False, since + this structure object does not support symmetry. """ return False @@ -295,33 +327,44 @@ def _get_srreal_structure(self): """ return self.stru - def getLattice(self): - """Get the ParameterSet containing the lattice Parameters.""" + def get_lattice(self): + """Return a ParameterSet holding a unit cubic lattice. + + A molecule is not periodic, so this returns a new ParameterSet + with a = b = c = 1 and alpha = beta = gamma = 90 degrees. + + Returns + ------- + ParameterSet + The ParameterSet holding the placeholder lattice Parameters. + """ lattice = ParameterSet("lattice") - lattice.newPar("a", 1.0) - lattice.newPar("b", 1.0) - lattice.newPar("c", 1.0) - lattice.newPar("alpha", 90) - lattice.newPar("beta", 90) - lattice.newPar("gamma", 90) + lattice.new_parameter("a", 1.0) + lattice.new_parameter("b", 1.0) + lattice.new_parameter("c", 1.0) + lattice.new_parameter("alpha", 90) + lattice.new_parameter("beta", 90) + lattice.new_parameter("gamma", 90) lattice.angunits = "deg" return lattice - def getScatterers(self): - """Get a list of ParameterSets that represents the scatterers. + def get_scatterers(self): + """Return the list of ParameterSets that represent the + scatterers. - The site positions must be accessible from the list entries via - the names "x", "y", and "z". The ADPs must be accessible as - well, but the name and nature of the ADPs (U-factors, B-factors, - isotropic, anisotropic) depends on the adapted structure. + Returns + ------- + list of ObjCrystMolAtomParSet + The atom ParameterSets of the molecule. """ return self.atoms - def wrapRestraints(self): + def wrap_restraints(self): """Wrap the restraints implicit to the molecule. - This will wrap MolBonds, MolBondAngles and MolDihedralAngles of - the Molecule as ObjCrystMoleculeRestraint objects. + This wraps the MolBonds, MolBondAngles and MolDihedralAngles of + the Molecule as ObjCrystMoleculeRestraint objects. Restraints + wrapped this way cannot be modified from within this class. """ # Wrap restraints. Restraints wrapped in this way cannot be modified # from within this class. @@ -339,19 +382,19 @@ def wrapRestraints(self): return - def wrapStretchModeParameters(self): + def wrap_stretch_mode_parameters(self): """Wrap the stretch modes implicit to the Molecule as Parameters. - This will wrap StretchModeBondLengths and StretchModeBondAngles of the - Molecule as Parameters. Note that this requires that the MolBondAtoms - in the Molecule came in with unique names. Torsion angles are not - wrapped, as there is not enough information to determine each MolAtom - in the angle. + This wraps the StretchModeBondLengths and StretchModeBondAngles + of the Molecule as Parameters. The MolBondAtoms in the Molecule + must have unique names. Torsion angles are not wrapped, as there + is not enough information to determine each MolAtom in the + angle. - The Parameters will be given the concatenated name of its constituents. - bond lengths: "bl_aname1_aname2" - bond angles: "ba_aname1_aname2_aname3" + Each Parameter is named after its constituent atoms, as + "bl_aname1_aname2" for bond lengths and + "ba_aname1_aname2_aname3" for bond angles. """ for mode in self.scat.GetStretchModeBondLengthList(): name1 = mode.mpAtom0.GetName() @@ -398,36 +441,35 @@ def wrapStretchModeParameters(self): return - def restrainBondLength( + def restrain_bond_length( self, atom1, atom2, length, sigma, delta, scaled=False ): - """Add a bond length restraint. + """Add a bond length restraint between two atoms. - This creates an instance of ObjCrystBondLengthRestraint and adds it to - the ObjCrystMoleculeParSet. + This creates an ObjCrystBondLengthRestraint and adds it to the + ObjCrystMoleculeParSet. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the bond - atom2 - Second atom (ObjCrystMolAtomParSet) in the bond - length - The length of the bond (Angstroms) - sigma - The uncertainty of the bond length (Angstroms) - delta - The width of the bond (Angstroms) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second atom in the bond. + length : float + The length of the bond in Angstroms. + sigma : float + The uncertainty of the bond length in Angstroms. + delta : float + The width of the bond in Angstroms. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) - (default False) + (default False). Returns ------- - res - The ObjCrystBondLengthRestraint object for use with the - 'unrestrain' method. + ObjCrystBondLengthRestraint + The restraint, for use with the ``unrestrain`` method. """ res = ObjCrystBondLengthRestraint( atom1, atom2, length, sigma, delta, scaled @@ -436,72 +478,70 @@ def restrainBondLength( return res - def restrainBondLengthParameter( + def restrain_bond_length_parameter( self, par, length, sigma, delta, scaled=False ): - """Add a bond length restraint. + """Add a bond length restraint on a bond length Parameter. - This creates an instance of ObjCrystBondLengthRestraint and adds it to - the ObjCrystMoleculeParSet. + This creates an ObjCrystBondLengthRestraint between the atoms of + `par` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par - A ObjCrystBondLengthParameter (see addBondLengthParameter) - length - The length of the bond (Angstroms) - sigma - The uncertainty of the bond length (Angstroms) - delta - The width of the bond (Angstroms) - scaled - A flag indicating if the restraint is scaled (multiplied) + par : ObjCrystBondLengthParameter + The bond length Parameter to restrain (see + add_bond_length_parameter). + length : float + The length of the bond in Angstroms. + sigma : float + The uncertainty of the bond length in Angstroms. + delta : float + The width of the bond in Angstroms. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) - (default False) + (default False). Returns ------- - ObjCrystBondLengthRestraint object - Returns the ObjCrystBondLengthRestraint object for use with the - 'unrestrain' method. + ObjCrystBondLengthRestraint + The restraint, for use with the ``unrestrain`` method. """ - return self.restrainBondLength( + return self.restrain_bond_length( par.atom1, par.atom2, length, sigma, delta, scaled ) - def restrainBondAngle( + def restrain_bond_angle( self, atom1, atom2, atom3, angle, sigma, delta, scaled=False ): - """Add a bond angle restraint. + """Add a bond angle restraint between three atoms. - This creates an instance of ObjCrystBondAngleRestraint and adds it to - the ObjCrystMoleculeParSet. + This creates an ObjCrystBondAngleRestraint and adds it to the + ObjCrystMoleculeParSet. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the bond angle - atom2 - Second (central) atom (ObjCrystMolAtomParSet) in the bond - angle - atom3 - Third atom (ObjCrystMolAtomParSet) in the bond angle - angle - The bond angle (radians) - sigma - The uncertainty of the bond angle (radians) - delta - The width of the bond angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the bond angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the bond angle. + atom3 : ObjCrystMolAtomParSet + The third atom in the bond angle. + angle : float + The bond angle in radians. + sigma : float + The uncertainty of the bond angle in radians. + delta : float + The width of the bond angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). Returns ------- - ObjCrystBondAngleRestraint object - Returns the ObjCrystBondAngleRestraint object for use with the - 'unrestrain' method. + ObjCrystBondAngleRestraint + The restraint, for use with the ``unrestrain`` method. """ res = ObjCrystBondAngleRestraint( atom1, atom2, atom3, angle, sigma, delta, scaled @@ -510,73 +550,72 @@ def restrainBondAngle( return res - def restrainBondAngleParameter( + def restrain_bond_angle_parameter( self, par, angle, sigma, delta, scaled=False ): - """Add a bond angle restraint. + """Add a bond angle restraint on a bond angle Parameter. - This creates an instance of ObjCrystBondAngleRestraint and adds it to - the ObjCrystMoleculeParSet. + This creates an ObjCrystBondAngleRestraint between the atoms of + `par` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par - A ObjCrystBondAngleParameter (see addBondAngleParameter) - angle - The bond angle (radians) - sigma - The uncertainty of the bond angle (radians) - delta - The width of the bond angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + par : ObjCrystBondAngleParameter + The bond angle Parameter to restrain (see + add_bond_angle_parameter). + angle : float + The bond angle in radians. + sigma : float + The uncertainty of the bond angle in radians. + delta : float + The width of the bond angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). Returns ------- - ObjCrystBondAngleRestraint object - Returns the ObjCrystBondAngleRestraint object for use with the - 'unrestrain' method. + ObjCrystBondAngleRestraint + The restraint, for use with the ``unrestrain`` method. """ - return self.restrainBondAngle( + return self.restrain_bond_angle( par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled ) - def restrainDihedralAngle( + def restrain_dihedral_angle( self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False ): - """Add a dihedral angle restraint. + """Add a dihedral angle restraint between four atoms. - This creates an instance of ObjCrystDihedralAngleRestraint and adds it - to the ObjCrystMoleculeParSet. + This creates an ObjCrystDihedralAngleRestraint and adds it to the + ObjCrystMoleculeParSet. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the angle - atom2 - Second (central) atom (ObjCrystMolAtomParSet) in the angle - atom3 - Third (central) atom (ObjCrystMolAtomParSet) in the angle - atom4 - Fourth atom in the angle (ObjCrystMolAtomParSet) - angle - The dihedral angle (radians) - sigma - The uncertainty of the dihedral angle (radians) - delta - The width of the dihedral angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the angle. + atom4 : ObjCrystMolAtomParSet + The fourth atom in the angle. + angle : float + The dihedral angle in radians. + sigma : float + The uncertainty of the dihedral angle in radians. + delta : float + The width of the dihedral angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). Returns ------- - ObjCrystDihedralAngleRestraint object - Returns the ObjCrystDihedralAngleRestraint object for use with the - 'unrestrain' method. + ObjCrystDihedralAngleRestraint + The restraint, for use with the ``unrestrain`` method. """ res = ObjCrystDihedralAngleRestraint( atom1, atom2, atom3, atom4, angle, sigma, delta, scaled @@ -585,37 +624,36 @@ def restrainDihedralAngle( return res - def restrainDihedralAngleParameter( + def restrain_dihedral_angle_parameter( self, par, angle, sigma, delta, scaled=False ): - """Add a dihedral angle restraint. + """Add a dihedral angle restraint on a dihedral angle Parameter. - This creates an instance of ObjCrystDihedralAngleRestraint and adds it - to the ObjCrystMoleculeParSet. + This creates an ObjCrystDihedralAngleRestraint between the atoms of + `par` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par - A ObjCrystDihedralAngleParameter (see - addDihedralAngleParameter) - angle - The dihedral angle (radians) - sigma - The uncertainty of the dihedral angle (radians) - delta - The width of the dihedral angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + par : ObjCrystDihedralAngleParameter + The dihedral angle Parameter to restrain (see + add_dihedral_angle_parameter). + angle : float + The dihedral angle in radians. + sigma : float + The uncertainty of the dihedral angle in radians. + delta : float + The width of the dihedral angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). Returns ------- - ObjCrystDihedralAngleRestraint object - Returns the ObjCrystDihedralAngleRestraint object for use with the - 'unrestrain' method. + ObjCrystDihedralAngleRestraint + The restraint, for use with the ``unrestrain`` method. """ - return self.restrainDihedralAngle( + return self.restrain_dihedral_angle( par.atom1, par.atom2, par.atom3, @@ -626,73 +664,68 @@ def restrainDihedralAngleParameter( scaled, ) - def addBondLengthParameter( + def add_bond_length_parameter( self, name, atom1, atom2, value=None, const=False ): - """Add a bond length to the Molecule. + """Add a refinable bond length to the Molecule. - This creates a ObjCrystBondLengthParameter to the + This adds an ObjCrystBondLengthParameter to the ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- - name - The name of the ObjCrystBondLengthParameter - atom1 - The first atom (ObjCrystMolAtomParSet) in the bond - atom2 - The second (mutated) atom (ObjCrystMolAtomParSet) in the - bond - value - An initial value for the bond length. If this is None - (default), then the current distance between the atoms will - be used. - const - A flag indicating whether the Parameter is constant - (default False) + name : str + The name of the new Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second (mutated) atom in the bond. + value : float, optional + The initial bond length. If None (default), the current + distance between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant + (default False). Returns ------- - ObjCrystBondLengthParameter object - Returns the new ObjCrystBondLengthParameter. + ObjCrystBondLengthParameter + The new bond length Parameter. """ par = ObjCrystBondLengthParameter(name, atom1, atom2, value, const) self.add_parameter(par) return par - def addBondAngleParameter( + def add_bond_angle_parameter( self, name, atom1, atom2, atom3, value=None, const=False ): - """Add a bond angle to the Molecule. + """Add a refinable bond angle to the Molecule. - This creates a ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet - that can be adjusted during the fit. + This adds an ObjCrystBondAngleParameter to the + ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- - name - The name of the ObjCrystBondAngleParameter - atom1 - The first atom (ObjCrystMolAtomParSet) in the bond angle - atom2 - The second (central) atom (ObjCrystMolAtomParSet) in the - bond angle - atom3 - The third (mutated) atom (ObjCrystMolAtomParSet) in the - bond angle - value - An initial value for the bond angle. If this is None - (default), then the current bond angle between the atoms - will be used. - const - A flag indicating whether the Parameter is constant + name : str + The name of the new Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the bond angle. + atom3 : ObjCrystMolAtomParSet + The third (mutated) atom in the bond angle. + value : float, optional + The initial bond angle in radians. If None (default), the + current bond angle between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant (default False). Returns ------- - ObjCrystBondAngleParameter object - Returns the new ObjCrystBondAngleParameter. + ObjCrystBondAngleParameter + The new bond angle Parameter. """ par = ObjCrystBondAngleParameter( name, atom1, atom2, atom3, value, const @@ -701,42 +734,37 @@ def addBondAngleParameter( return par - def addDihedralAngleParameter( + def add_dihedral_angle_parameter( self, name, atom1, atom2, atom3, atom4, value=None, const=False ): - """Add a dihedral angle to the Molecule. + """Add a refinable dihedral angle to the Molecule. - This creates a ObjCrystDihedralAngleParameter to the + This adds an ObjCrystDihedralAngleParameter to the ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- - name - The name of the ObjCrystDihedralAngleParameter. - atom1 - The first atom (ObjCrystMolAtomParSet) in the dihderal - angle. - atom2 - The second (central) atom (ObjCrystMolAtomParSet) in the - dihderal angle - atom3 - The third (central) atom (ObjCrystMolAtomParSet) in the - dihderal angle - atom4 - The fourth (mutated) atom (ObjCrystMolAtomParSet) in the - dihderal angle - value - An initial value for the dihedral angle. If this is None - (default), then the current dihedral angle between atoms - will be used. - const - A flag indicating whether the Parameter is constant + name : str + The name of the new Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the dihedral angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the dihedral angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the dihedral angle. + atom4 : ObjCrystMolAtomParSet + The fourth (mutated) atom in the dihedral angle. + value : float, optional + The initial dihedral angle in radians. If None (default), the + current dihedral angle between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant (default False). Returns ------- - ObjCrystDihedralAngleParameter object - Returns the new ObjCrystDihedralAngleParameter. + ObjCrystDihedralAngleParameter + The new dihedral angle Parameter. """ par = ObjCrystDihedralAngleParameter( name, atom1, atom2, atom3, atom4, value, const @@ -750,46 +778,44 @@ def addDihedralAngleParameter( class ObjCrystMolAtomParSet(ObjCrystScattererParSet): - """A adaptor for an pyobjcryst.molecule.MolAtom. + """Adapt a pyobjcryst.molecule.MolAtom to the ParameterSet + interface. - This class derives from srfit.fitbase.parameterset.ParameterSet. Note that - MolAtom does not derive from Scatterer, but the relevant interface is the - same within pyobjcryst. See the ParameterSet class for base attributes. + This class derives from ObjCrystScattererParSet. MolAtom does not + derive from Scatterer, but the relevant interface is the same within + pyobjcryst. See the ParameterSet class for base attributes. Attributes ---------- - scat - The adapted pyobjcryst.molecule.MolAtom. - parent - The ObjCrystCrystalParSet this belongs to - element - Non-refinable name of the element (property). - - Managed Parameters - ------------------ - occ - Occupancy of the atom on its crystal location - (ParameterAdapter) - Biso - Isotropic scattering factor (ParameterAdapter). This does - not exist for dummy atoms. See the 'isDummy' method. - B11, B22, B33, B12, B21, B23, B32, B13, B31 - -- Anisotropic displacement factor for scatterer - (ParameterAdapter or ParameterProxy). Note that the Bij and Bji - parameters are the same. + scat : pyobjcryst.molecule.MolAtom + The adapted MolAtom. + parent : ObjCrystMoleculeParSet + The molecule ParameterSet this belongs to. + element : str + The non-refinable name of the element, or "dummy" for a dummy + atom (property). + occ : ParameterAdapter + The occupancy of the atom on its crystal location. + Biso : ParameterAdapter + The isotropic displacement factor of the atom. This does not + exist for dummy atoms; see the ``is_dummy`` method. + Bij : ParameterAdapter or ParameterProxy + The anisotropic displacement factors B11, B22, B33, B12, B21, B13, + B31, B23 and B32 of the atom. The Bij and Bji parameters are the + same. These do not exist for dummy atoms. """ def __init__(self, name, scat, parent): - """Initialize. + """Initialize the MolAtom ParameterSet. Parameters ---------- - name - The name of the scatterer - scat - The Scatterer instance - parent - The ObjCrystCrystalParSet this belongs to + name : str + The name of the atom. + scat : pyobjcryst.molecule.MolAtom + The MolAtom to adapt. + parent : ObjCrystMoleculeParSet + The molecule ParameterSet this belongs to. """ ObjCrystScattererParSet.__init__(self, name, scat, parent) sp = scat.GetScatteringPower() @@ -825,8 +851,14 @@ def _getelem(self): element = property(_getelem) - def isDummy(self): - """Indicate whether this atom is a dummy atom.""" + def is_dummy(self): + """Return whether this atom is a dummy atom. + + Returns + ------- + bool + The flag indicating if this is a dummy atom. + """ return self.scat.IsDummy() @@ -836,30 +868,30 @@ def isDummy(self): class ObjCrystMoleculeRestraint(object): """Base class for adapting pyobjcryst Molecule restraints to srfit. - The 'penalty' method calls 'GetLogLikelihood' of the pyobjcryst restraint. - This implements the 'penalty' method from - diffpy.srfit.fitbase.restraint.Restraint. The 'restrain' method is not - needed or implemented. + This implements the ``penalty`` method of + diffpy.srfit.fitbase.restraint.Restraint by calling + ``GetLogLikelihood`` of the pyobjcryst restraint. The ``restrain`` + method is not needed or implemented. Attributes ---------- - res + res : object The pyobjcryst Molecule restraint. - scaled - A flag indicating if the restraint is scaled (multiplied) by + scaled : bool + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ def __init__(self, res, scaled=False): - """Create a Restraint-like from a pyobjcryst Molecule restraint. + """Wrap a pyobjcryst Molecule restraint as a Restraint. Parameters ---------- - res + res : object The pyobjcryst Molecule restraint. - scaled - A flag indicating if the restraint is scaled (multiplied) + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ @@ -872,9 +904,15 @@ def penalty(self, w=1.0): Parameters ---------- - w + w : float, optional The point-average chi^2 which is optionally used to scale the penalty (default 1.0). + + Returns + ------- + float + The log-likelihood of the pyobjcryst restraint, optionally + scaled by `w`. """ penalty = self.res.GetLogLikelihood() if self.scaled: @@ -890,43 +928,43 @@ class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): Attributes ---------- - atom1 - The first atom in the bond (ObjCrystMolAtomParSet) - atom2 - The second atom in the bond (ObjCrystMolAtomParSet) - length - The length of the bond (Angstroms) - sigma - The uncertainty of the bond length (Angstroms) - delta - The width of the bond (Angstroms) - res - The pyobjcryst BondLength restraint - scaled - A flag indicating if the restraint is scaled (multiplied) by + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second atom in the bond. + length : float + The length of the bond in Angstroms. + sigma : float + The uncertainty of the bond length in Angstroms. + delta : float + The width of the bond in Angstroms. + res : pyobjcryst.molecule.MolBond + The pyobjcryst bond length restraint. + scaled : bool + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default - False) + False). """ def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): - """Create a bond length restraint. + """Initialize the bond length restraint. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the bond - atom2 - Second atom (ObjCrystMolAtomParSet) in the bond - length - The length of the bond (Angstroms) - sigma - The uncertainty of the bond length (Angstroms) - delta - The width of the bond (Angstroms) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second atom in the bond. + length : float + The length of the bond in Angstroms. + sigma : float + The uncertainty of the bond length in Angstroms. + delta : float + The width of the bond in Angstroms. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) - (default False) + (default False). """ self.atom1 = atom1 self.atom2 = atom2 @@ -960,46 +998,45 @@ class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): Attributes ---------- - atom1 - The first atom in the angle (ObjCrystMolAtomParSet) - atom2 - The second atom in the angle (ObjCrystMolAtomParSet) - atom3 - The third atom in the angle (ObjCrystMolAtomParSet) - angle - The bond angle (radians) - sigma - The uncertainty of the bond angle (radians) - delta - The width of the bond angle (radians) - res - The pyobjcryst BondAngle restraint - scaled - A flag indicating if the restraint is scaled (multiplied) by + atom1 : ObjCrystMolAtomParSet + The first atom in the angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the angle. + atom3 : ObjCrystMolAtomParSet + The third atom in the angle. + angle : float + The bond angle in radians. + sigma : float + The uncertainty of the bond angle in radians. + delta : float + The width of the bond angle in radians. + res : pyobjcryst.molecule.MolBondAngle + The pyobjcryst bond angle restraint. + scaled : bool + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default - False) + False). """ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): - """Create a bond angle restraint. + """Initialize the bond angle restraint. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the bond angle - atom2 - Second (central) atom (ObjCrystMolAtomParSet) in the bond - angle - atom3 - Third atom (ObjCrystMolAtomParSet) in the bond angle - angle - The bond angle (radians) - sigma - The uncertainty of the bond angle (radians) - delta - The width of the bond angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the bond angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the bond angle. + atom3 : ObjCrystMolAtomParSet + The third atom in the bond angle. + angle : float + The bond angle in radians. + sigma : float + The uncertainty of the bond angle in radians. + delta : float + The width of the bond angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ @@ -1038,51 +1075,51 @@ class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): Attributes ---------- - atom1 - The first atom in the angle (ObjCrystMolAtomParSet) - atom2 - The second (central) atom in the angle (ObjCrystMolAtomParSet) - atom3 - The third (central) atom in the angle (ObjCrystMolAtomParSet) - atom4 - The fourth atom in the angle (ObjCrystMolAtomParSet) - angle - The dihedral angle (radians) - sigma - The uncertainty of the dihedral angle (radians) - delta - The width of the dihedral angle (radians) - res - The pyobjcryst DihedralAngle restraint - scaled - A flag indicating if the restraint is scaled (multiplied) by + atom1 : ObjCrystMolAtomParSet + The first atom in the angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the angle. + atom4 : ObjCrystMolAtomParSet + The fourth atom in the angle. + angle : float + The dihedral angle in radians. + sigma : float + The uncertainty of the dihedral angle in radians. + delta : float + The width of the dihedral angle in radians. + res : pyobjcryst.molecule.MolDihedralAngle + The pyobjcryst dihedral angle restraint. + scaled : bool + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default - False) + False). """ def __init__( self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False ): - """Create a dihedral angle restraint. + """Initialize the dihedral angle restraint. Parameters ---------- - atom1 - First atom (ObjCrystMolAtomParSet) in the angle - atom2 - Second (central) atom (ObjCrystMolAtomParSet) in the angle - atom3 - Third (central) atom (ObjCrystMolAtomParSet) in the angle - atom4 - Fourth atom in the angle (ObjCrystMolAtomParSet) - angle - The dihedral angle (radians) - sigma - The uncertainty of the dihedral angle (radians) - delta - The width of the dihedral angle (radians) - scaled - A flag indicating if the restraint is scaled (multiplied) + atom1 : ObjCrystMolAtomParSet + The first atom in the angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the angle. + atom4 : ObjCrystMolAtomParSet + The fourth atom in the angle. + angle : float + The dihedral angle in radians. + sigma : float + The uncertainty of the dihedral angle in radians. + delta : float + The width of the dihedral angle in radians. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ @@ -1120,45 +1157,62 @@ def __init__( class StretchModeParameter(Parameter): """Partial Parameter class encapsulating pyobjcryst stretch modes. - This class relies upon attributes that do not belong to it. Do not - instantiate this class. - - Required attributes - ------------------- - matoms - The set of all mutated AtomParSets - molecule - The ObjCrystMoleculeParSet the atoms belong to - mode - The pyobjcryst.molecule.StretchMode used to change atomic - positions. - keepcenter - Flag indicating whether to keep the center of mass of the - molecule stationary within the crystal when changing the - value of the parameter (bool, default True). + This class relies upon attributes that subclasses must set before + calling ``StretchModeParameter.__init__``. Do not instantiate this + class directly. + + Attributes + ---------- + matoms : set of ObjCrystMolAtomParSet + The set of all mutated atoms. Set by the subclass. + molecule : ObjCrystMoleculeParSet + The molecule the atoms belong to. Set by the subclass. + mode : pyobjcryst.molecule.StretchMode + The stretch mode used to change atomic positions. Set by the + subclass. + keepcenter : bool + The flag indicating whether to keep the center of mass of the + molecule stationary within the crystal when changing the value + of the parameter (default True). """ def __init__(self, name, value=None, const=False): - """Initialization. + """Initialize the stretch mode Parameter. Parameters ---------- - name - The name of this Parameter (must be a valid attribute - identifier) - value - The initial value of this Parameter (default 0). - const - A flag inticating whether the Parameter is a constant (like - pi). - - Raises ValueError if the name is not a valid attribute identifier + name : str + The name of this Parameter. It must be a valid attribute + identifier. + value : float, optional + The initial value of this Parameter (default None). + const : bool, optional + The flag indicating whether the Parameter is a constant + (default False). + + Raises + ------ + ValueError + If `name` is not a valid attribute identifier. """ Parameter.__init__(self, name, value, const) self.keepcenter = True def set_value(self, val): - """Change the value of the Parameter.""" + """Set the value of the Parameter by stretching the molecule. + + The stretch mode moves the mutated atoms by the change in value. + + Parameters + ---------- + val : float + The new value of the Parameter. + + Returns + ------- + StretchModeParameter + Return self so that mutators can be chained. + """ curval = self.get_value() val = float(val) @@ -1174,14 +1228,22 @@ def set_value(self, val): return self - def addAtoms(self, atomlist): - """Associate ObjCrystMolAtomParSets with the Parameter. + def add_atoms(self, atomlist): + """Associate additional atoms with the Parameter. + + The added atoms are mutated in exactly the same way as the + primary mutated atom. This is useful when a group of atoms should + move rigidly in response to a change in a bond property. - This will associate additional ObjCrystMolAtomParSets with the - Parameter. These will be mutated in the exact same way as the - primary mutated ObjCrystMolAtomParSet. This is useful when a - group of atoms should move rigidly in response to a change in a - bond property. + Parameters + ---------- + atomlist : ObjCrystMolAtomParSet or list of ObjCrystMolAtomParSet + The atom or atoms to associate with the Parameter. + + Returns + ------- + StretchModeParameter + Return self so that mutators can be chained. """ if not hasattr(atomlist, "__iter__"): atomlist = [atomlist] @@ -1201,9 +1263,14 @@ def addAtoms(self, atomlist): def notify(self, other=()): """Notify all mutated Parameters and observers. - Some of the mutated parameters will be observing us. At the same - time we need to observe them. Observable won't let us do both, - so we notify the Parameters that we mutate directly. + Some of the mutated Parameters observe this Parameter while this + Parameter also observes them. Observable does not allow both, so + the mutated Parameters are notified directly. + + Parameters + ---------- + other : tuple, optional + The objects that have already been notified (default empty). """ noneother = () # Notify the atoms that have moved @@ -1225,7 +1292,7 @@ def notify(self, other=()): class ObjCrystBondLengthParameter(StretchModeParameter): - """Class for abstracting a bond length in a Molecule to a Parameter. + """Represent a bond length in a Molecule as a Parameter. This wraps up a pyobjcryst.molecule.StretchModeBondLength object so that the distance between two MolAtoms in a Molecule can be used as an @@ -1253,60 +1320,52 @@ class ObjCrystBondLengthParameter(StretchModeParameter): Attributes ---------- - atom1 - The first ObjCrystMolAtomParSet in the bond - atom2 - The second (mutated) ObjCrystMolAtomParSet in the bond - matoms - The set of all mutated ObjCrystMolAtomParSets - molecule - The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets - belong to - mode - The pyobjcryst.molecule.StretchModeBondLength for the bond - - Inherited Attributes - -------------------- - name - A name for this Parameter. - const - A flag indicating whether this is considered a constant. - _value - The value of the Parameter. Modified with 'set_value'. - value - Property for 'get_value' and 'set_value'. - constraint - A callable that calculates the value of this Parameter. If - this is None (None), the the Parameter is responsible for its - own value. The callable takes no arguments. - bounds - A 2-list defining the bounds on the Parameter. This can be - used by some optimizers when the Parameter is varied. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second (mutated) atom in the bond. + matoms : set of ObjCrystMolAtomParSet + The set of all mutated atoms. + molecule : ObjCrystMoleculeParSet + The molecule the atoms belong to. + mode : pyobjcryst.molecule.StretchModeBondLength + The stretch mode for the bond. + name : str + The name of this Parameter (inherited). + const : bool + The flag indicating whether this is considered a constant + (inherited). + value : float + The property for ``get_value`` and ``set_value`` (inherited). + constraint : callable or None + The callable that calculates the value of this Parameter. If + None, the Parameter is responsible for its own value + (inherited). + bounds : list of float + The lower and upper bounds on the Parameter, which some + optimizers use when the Parameter is varied (inherited). """ def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): - """Create a ObjCrystBondLengthParameter. + """Initialize the bond length Parameter. Parameters ---------- - name - The name of the ObjCrystBondLengthParameter - atom1 - The first atom (ObjCrystMolAtomParSet) in the bond - atom2 - The second (mutated) atom (ObjCrystMolAtomParSet) in the - bond - value - An initial value for the bond length. If this is None - (default), then the current distance between the atoms will - be used. - const - A flag indicating whether the Parameter is constant - (default False) - mode - An extant pyobjcryst.molecule.StretchModeBondLength to use. - If this is None (default), then a new StretchModeBondLength - will be built. + name : str + The name of the Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond. + atom2 : ObjCrystMolAtomParSet + The second (mutated) atom in the bond. + value : float, optional + The initial bond length. If None (default), the current + distance between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant + (default False). + mode : pyobjcryst.molecule.StretchModeBondLength, optional + The existing stretch mode to use. If None (default), a new + StretchModeBondLength is built. """ # Create the mode self.mode = mode @@ -1342,18 +1401,18 @@ def set_constant(self, is_constant=True, value=None): Parameters ---------- - is_constant - Flag indicating if the Parameter is constant (default + is_constant : bool, optional + The flag indicating if the Parameter is constant (default True). - value - An optional value for the Parameter (default None). If this - is not None, then the Parameter will get a new value, - constant or otherwise. + value : float, optional + The value to set the Parameter to (default None). If this is + not None, the Parameter gets a new value, constant or + otherwise. - Return - ------ - self - Returns self so that mutators can be chained. + Returns + ------- + StretchModeParameter + Return self so that mutators can be chained. """ StretchModeParameter.set_constant(self, is_constant, value) @@ -1364,11 +1423,15 @@ def set_constant(self, is_constant=True, value=None): return self def get_value(self): - """This calculates the value if it might have been changed. + """Return the bond length, recalculating it if needed. + + The atoms underlying the bond may have moved, so the bond length + is recalculated whenever the cached value has been cleared. - There is no guarantee that the ObjCrystMolAtomParSets underlying - the bond won't change, so the bond length is calculated if - necessary each time this is called. + Returns + ------- + float + The bond length in Angstroms. """ if self._value is None: val = GetBondLength(self.atom1.scat, self.atom2.scat) @@ -1381,7 +1444,7 @@ def get_value(self): class ObjCrystBondAngleParameter(StretchModeParameter): - """Class for abstracting a bond angle in a Molecule to a Parameter. + """Represent a bond angle in a Molecule as a Parameter. This wraps up a pyobjcryst.molecule.StretchModeBondAngle object so that the angle defined by three MolAtoms in a Molecule can be used as an adjustable @@ -1394,66 +1457,58 @@ class ObjCrystBondAngleParameter(StretchModeParameter): Attributes ---------- - atom1 - The first ObjCrystAtomParSet in the bond angle - atom2 - The second (central) ObjCrystMolAtomParSet in the bond angle - atom3 - The third (mutated) ObjCrystMolAtomParSet in the bond angle - matoms - The set of all mutated ObjCrystMolAtomParSets - molecule - The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets - belong to - mode - The pyobjcryst.molecule.StretchModeBondAngle for the bond angle - - Inherited Attributes - -------------------- - name - A name for this Parameter. - const - A flag indicating whether this is considered a constant. - _value - The value of the Parameter. Modified with 'set_value'. - value - Property for 'get_value' and 'set_value'. - constraint - A callable that calculates the value of this Parameter. If - this is None (None), the the Parameter is responsible for its - own value. The callable takes no arguments. - bounds - A 2-list defining the bounds on the Parameter. This can be - used by some optimizers when the Parameter is varied. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the bond angle. + atom3 : ObjCrystMolAtomParSet + The third (mutated) atom in the bond angle. + matoms : set of ObjCrystMolAtomParSet + The set of all mutated atoms. + molecule : ObjCrystMoleculeParSet + The molecule the atoms belong to. + mode : pyobjcryst.molecule.StretchModeBondAngle + The stretch mode for the bond angle. + name : str + The name of this Parameter (inherited). + const : bool + The flag indicating whether this is considered a constant + (inherited). + value : float + The property for ``get_value`` and ``set_value`` (inherited). + constraint : callable or None + The callable that calculates the value of this Parameter. If + None, the Parameter is responsible for its own value + (inherited). + bounds : list of float + The lower and upper bounds on the Parameter, which some + optimizers use when the Parameter is varied (inherited). """ def __init__( self, name, atom1, atom2, atom3, value=None, const=False, mode=None ): - """Create a ObjCrystBondAngleParameter. + """Initialize the bond angle Parameter. Parameters ---------- - name - The name of the ObjCrystBondAngleParameter. - atom1 - The first atom (ObjCrystMolAtomParSet) in the bond angle - atom2 - The second (central) atom (ObjCrystMolAtomParSet) in the - bond angle - atom3 - The third (mutated) atom (ObjCrystMolAtomParSet) in the - bond angle - value - An initial value for the bond length. If this is None - (default), then the current bond angle between the atoms - will be used. - const - A flag indicating whether the Parameter is constant + name : str + The name of the Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the bond angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the bond angle. + atom3 : ObjCrystMolAtomParSet + The third (mutated) atom in the bond angle. + value : float, optional + The initial bond angle in radians. If None (default), the + current bond angle between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant (default False). - mode - A pre-built mode to place in this Parameter. If this is - None (default), then a StretchMode will be built. + mode : pyobjcryst.molecule.StretchModeBondAngle, optional + The existing stretch mode to use. If None (default), a new + StretchModeBondAngle is built. """ # Create the stretch mode self.mode = mode @@ -1492,18 +1547,18 @@ def set_constant(self, is_constant=True, value=None): Parameters ---------- - is_constant - Flag indicating if the Parameter is constant (default + is_constant : bool, optional + The flag indicating if the Parameter is constant (default True). - value - An optional value for the Parameter (default None). If this - is not None, then the Parameter will get a new value, - constant or otherwise. + value : float, optional + The value to set the Parameter to (default None). If this is + not None, the Parameter gets a new value, constant or + otherwise. - Return - ------ - self - Returns self so that mutators can be chained. + Returns + ------- + StretchModeParameter + Return self so that mutators can be chained. """ StretchModeParameter.set_constant(self, is_constant, value) for a in [self.atom1, self.atom2, self.atom3]: @@ -1513,11 +1568,15 @@ def set_constant(self, is_constant=True, value=None): return self def get_value(self): - """This calculates the value if it might have been changed. + """Return the bond angle, recalculating it if needed. + + The atoms underlying the bond angle may have moved, so the angle + is recalculated whenever the cached value has been cleared. - There is no guarantee that the MolAtoms underlying the bond - angle won't change, so the bond angle is calculated if necessary - each time this is called. + Returns + ------- + float + The bond angle in radians. """ if self._value is None: val = GetBondAngle( @@ -1532,8 +1591,7 @@ def get_value(self): class ObjCrystDihedralAngleParameter(StretchModeParameter): - """Class for abstracting a dihedral angle in a Molecule to a - Parameter. + """Represent a dihedral angle in a Molecule as a Parameter. This wraps up a pyobjcryst.molecule.StretchModeTorsion object so that the angle defined by four MolAtoms ([a1-a2].[a3-a4]) in a Molecule can be used @@ -1546,41 +1604,34 @@ class ObjCrystDihedralAngleParameter(StretchModeParameter): Attributes ---------- - atom1 - The first ObjCrystMolAtomParSet in the dihedral angle - atom2 - The second (central) ObjCrystMolAtomParSet in the dihedral - angle - atom3 - The third (central) ObjCrystMolAtomParSet in the dihedral angle - atom4 - The fourth (mutated) ObjCrystMolAtomParSet in the dihedral - angle - matoms - The set of all mutated ObjCrystMolAtomParSets - molecule - The ObjCrystMoleculeParSet the atoms belong to - mode - The pyobjcryst.molecule.StretchModeTorsion for the dihedral - angle - - Inherited Attributes - -------------------- - name - A name for this Parameter. - const - A flag indicating whether this is considered a constant. - _value - The value of the Parameter. Modified with 'set_value'. - value - Property for 'get_value' and 'set_value'. - constraint - A callable that calculates the value of this Parameter. If - this is None (None), the the Parameter is responsible for its - own value. The callable takes no arguments. - bounds - A 2-list defining the bounds on the Parameter. This can be - used by some optimizers when the Parameter is varied. + atom1 : ObjCrystMolAtomParSet + The first atom in the dihedral angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the dihedral angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the dihedral angle. + atom4 : ObjCrystMolAtomParSet + The fourth (mutated) atom in the dihedral angle. + matoms : set of ObjCrystMolAtomParSet + The set of all mutated atoms. + molecule : ObjCrystMoleculeParSet + The molecule the atoms belong to. + mode : pyobjcryst.molecule.StretchModeTorsion + The stretch mode for the dihedral angle. + name : str + The name of this Parameter (inherited). + const : bool + The flag indicating whether this is considered a constant + (inherited). + value : float + The property for ``get_value`` and ``set_value`` (inherited). + constraint : callable or None + The callable that calculates the value of this Parameter. If + None, the Parameter is responsible for its own value + (inherited). + bounds : list of float + The lower and upper bounds on the Parameter, which some + optimizers use when the Parameter is varied (inherited). """ def __init__( @@ -1594,34 +1645,29 @@ def __init__( const=False, mode=None, ): - """Create a ObjCrystDihedralAngleParameter. + """Initialize the dihedral angle Parameter. Parameters ---------- - name - The name of the ObjCrystDihedralAngleParameter - atom1 - The first atom (ObjCrystMolAtomParSet) in the dihderal - angle - atom2 - The second (central) atom (ObjCrystMolAtomParSet) in the - dihderal angle - atom3 - The third (central) atom (ObjCrystMolAtomParSet) in the - dihderal angle - atom4 - The fourth (mutated) atom (ObjCrystMolAtomParSet) in the - dihderal angle - value - An initial value for the bond length. If this is None - (default), then the current dihedral angle between atoms - will be used. - const - A flag indicating whether the Parameter is constant + name : str + The name of the Parameter. + atom1 : ObjCrystMolAtomParSet + The first atom in the dihedral angle. + atom2 : ObjCrystMolAtomParSet + The second (central) atom in the dihedral angle. + atom3 : ObjCrystMolAtomParSet + The third (central) atom in the dihedral angle. + atom4 : ObjCrystMolAtomParSet + The fourth (mutated) atom in the dihedral angle. + value : float, optional + The initial dihedral angle in radians. If None (default), the + current dihedral angle between the atoms is used. + const : bool, optional + The flag indicating whether the Parameter is constant (default False). - mode - A pre-built mode to place in this Parameter. If this is - None (default), then a StretchMode will be built. + mode : pyobjcryst.molecule.StretchModeTorsion, optional + The existing stretch mode to use. If None (default), a new + StretchModeTorsion is built. """ # Create the stretch mode self.mode = mode @@ -1656,22 +1702,23 @@ def __init__( def set_constant(self, is_constant=True, value=None): """Toggle the Parameter as constant. - This sets the underlying ObjCrystMolAtomParSet positions const as well. + This sets the underlying ObjCrystMolAtomParSet positions + constant as well. Parameters ---------- - is_constant - Flag indicating if the Parameter is constant (default + is_constant : bool, optional + The flag indicating if the Parameter is constant (default True). - value - An optional value for the Parameter (default None). If this - is not None, then the Parameter will get a new value, - constant or otherwise. + value : float, optional + The value to set the Parameter to (default None). If this is + not None, the Parameter gets a new value, constant or + otherwise. - Return - ------ - self - Returns self so that mutators can be chained. + Returns + ------- + StretchModeParameter + Return self so that mutators can be chained. """ StretchModeParameter.set_constant(self, is_constant, value) for a in [self.atom1, self.atom2, self.atom3, self.atom4]: @@ -1681,11 +1728,16 @@ def set_constant(self, is_constant=True, value=None): return self def get_value(self): - """This calculates the value if it might have been changed. + """Return the dihedral angle, recalculating it if needed. - There is no guarantee that the ObjCrystMolAtomParSets underlying - the dihedral angle won't change from some other Parameter, so - the value is recalculated each time. + The atoms underlying the dihedral angle may have been moved by + another Parameter, so the angle is recalculated whenever the + cached value has been cleared. + + Returns + ------- + float + The dihedral angle in radians. """ if self._value is None: val = GetDihedralAngle( @@ -1703,49 +1755,46 @@ def get_value(self): class ObjCrystCrystalParSet(SrRealParSet): - """A adaptor for pyobjcryst.crystal.Crystal instance. + """Adapt a pyobjcryst.crystal.Crystal to the ParameterSet interface. - This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. - See this class for base attributes. + This class derives from SrRealParSet. See that class for base + attributes. Attributes ---------- - stru - The adapted pyobjcryst.Crystal. - scatterers - The list of aggregated ScattererParSets (either - ObjCrystAtomParSet or ObjCrystMoleculeParSet), provided for - convenience. - _sgpars - A BaseSpaceGroupParameters object containing free structure - Parameters. See the diffpy.cmistructure.sgconstraints - module. - sgpars - property that creates _sgpars when it is needed. - angunits - "rad", the units of angle - - Parameters - ---------- - x - Scatterer position in crystal coordinates (ParameterWraper) - y - Scatterer position in crystal coordinates (ParameterWraper) - z - Scatterer position in crystal coordinates (ParameterWraper) - occ - Occupancy of the scatterer on its crystal site (ParameterWraper) + stru : pyobjcryst.crystal.Crystal + The adapted crystal. + scatterers : list of ObjCrystAtomParSet or ObjCrystMoleculeParSet + The scatterer ParameterSets, provided for convenience. + sgpars : SpaceGroupParameters + The free structure Parameters after applying the crystal's space + group constraints, created when first accessed. See the + diffpy.cmistructure.sgconstraints module. + angunits : str + The units of the lattice angles, always "rad". + a, b, c, alpha, beta, gamma : ParameterAdapter + The unit cell parameters. """ def __init__(self, name, cryst): - """Initialize. + """Initialize the crystal ParameterSet. Parameters ---------- - name - A name for this ParameterSet - cryst - An pyobjcryst.Crystal instance. + name : str + The name of this ParameterSet. + cryst : pyobjcryst.crystal.Crystal + The crystal to adapt. + + Raises + ------ + ValueError + If a scatterer in the crystal has no name, or if two + scatterers share a name. Give every scatterer a unique name + before wrapping the crystal. + TypeError + If the crystal contains a scatterer that is neither an Atom + nor a Molecule. """ SrRealParSet.__init__(self, name) self.angunits = "rad" @@ -1860,23 +1909,42 @@ def _create_space_group(sgobjcryst): return sg @classmethod - def canAdapt(self, stru): - """Return whether the structure can be adapted by this class.""" + def can_adapt(self, stru): + """Return whether the structure can be adapted by this class. + + Parameters + ---------- + stru : object + The structure object to check. + + Returns + ------- + bool + The flag indicating if `stru` is a pyobjcryst Crystal. + """ from pyobjcryst.crystal import Crystal return isinstance(stru, Crystal) - def getLattice(self): - """Get the ParameterSet containing the lattice Parameters.""" + def get_lattice(self): + """Return the ParameterSet containing the lattice Parameters. + + Returns + ------- + ObjCrystCrystalParSet + This ParameterSet, which holds the lattice Parameters + directly. + """ return self - def getScatterers(self): - """Get a list of ParameterSets that represents the scatterers. + def get_scatterers(self): + """Return the list of ParameterSets that represent the + scatterers. - The site positions must be accessible from the list entries via - the names "x", "y", and "z". The ADPs must be accessible as - well, but the name and nature of the ADPs (U-factors, B-factors, - isotropic, anisotropic) depends on the adapted structure. + Returns + ------- + list of ObjCrystAtomParSet or ObjCrystMoleculeParSet + The scatterer ParameterSets of the crystal. """ return self.scatterers diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index ad4a890..71b7a06 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -34,72 +34,70 @@ def constrain_as_space_group( adpsymbols=None, isosymbol="Uiso", ): - """Constrain the structure to the space group. + """Constrain a P1 structure to a space group. - This applies space group constraints to a StructureParSet with P1 - symmetry. Passed scatterers are explicitly constrained to the - specified space group. The ADPs and lattice may be constrained as well. + This applies space group constraints to a structure ParameterSet with + P1 symmetry. The passed scatterers are explicitly constrained to the + specified space group, and the ADPs and lattice may be constrained as + well. New Parameters used in the constraints are created within the + returned SpaceGroupParameters object. Constraints are created in the + ParameterSet that contains the constrained Parameter. This erases any + constraints or constant flags on the scatterers, lattice or ADPs that + are to be constrained. Parameters ---------- - phase - A BaseStructure object. - spacegroup - The space group number, symbol or an instance of - SpaceGroup class from diffpy.structure package. - sgoffset - Optional offset for sg origin (default [0, 0, 0]). - scatterers - The scatterer ParameterSets to constrain. If scatterers - is None (default), then all scatterers accessible from - phase.getScatterers will be constrained. - constrainlat - Flag indicating whether to constrain the lattice - (default True). - constrainadps - Flag indicating whether to constrain the ADPs - (default True). - adpsymbols - A list of the ADP names. By default this is equal to - diffpy.structure.symmetryutilities.stdUsymbols (U11, - U22, etc.). The names must be given in the same order - as stdUsymbols. - isosymbol - Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrained via the anisotropic ADPs. - - - New Parameters that are used in constraints are created within a - SpaceGroupParameters object, which is returned from this function. - Constraints are created in ParameterSet that contains the constrained - Parameter. This will erase any constraints or constant flags on the - scatterers, lattice or ADPs if they are to be constrained. - - The lattice constraints are applied as following. - - Crystal System - Triclinic - No constraints. - Monoclinic - alpha and beta are fixed to 90 unless alpha != beta and - alpha == gamma, in which case alpha and gamma are fixed - to 90. - Orthorhombic - alpha, beta and gamma are fixed to 90. - Tetragonal - b is constrained to a and alpha, beta and gamma are - fixed to 90. - Trigonal - If gamma == 120, then b is constrained to a, alpha - and beta are fixed to 90 and gamma is fixed to 120. - Otherwise, b and c are constrained to a, beta and gamma - are fixed to alpha. - Hexagonal - b is constrained to a, alpha and beta are fixed to 90 - and gamma is fixed to 120. - Cubic - b and c are constrained to a, and alpha, beta and - gamma are fixed to 90. + phase : BaseStructureParSet + The structure ParameterSet to constrain. + spacegroup : int, str or diffpy.structure.spacegroups.SpaceGroup + The space group number, symbol or SpaceGroup instance. + scatterers : list of ParameterSet, optional + The scatterer ParameterSets to constrain. If None (default), all + scatterers returned by ``phase.get_scatterers()`` are constrained. + sgoffset : list of float, optional + The offset of the space group origin (default [0, 0, 0]). + constrainlat : bool, optional + The flag indicating whether to constrain the lattice (default + True). + constrainadps : bool, optional + The flag indicating whether to constrain the ADPs (default True). + adpsymbols : list of str, optional + The ADP names. By default this is + diffpy.structure.symmetryutilities.stdUsymbols (U11, U22, etc.). + The names must be given in the same order as stdUsymbols. + isosymbol : str, optional + The name of the isotropic ADP (default "Uiso"). If None, + isotropic ADPs are constrained via the anisotropic ADPs. + + Returns + ------- + SpaceGroupParameters + The free Parameters of the structure that remain after applying + the space group constraints. + + Notes + ----- + The lattice constraints are applied as follows. + + Triclinic + No constraints. + Monoclinic + alpha and beta are fixed to 90 unless alpha != beta and + alpha == gamma, in which case alpha and gamma are fixed to 90. + Orthorhombic + alpha, beta and gamma are fixed to 90. + Tetragonal + b is constrained to a and alpha, beta and gamma are fixed to 90. + Trigonal + If gamma == 120, then b is constrained to a, alpha and beta are + fixed to 90 and gamma is fixed to 120. Otherwise, b and c are + constrained to a, and beta and gamma are fixed to alpha. + Hexagonal + b is constrained to a, alpha and beta are fixed to 90 and gamma + is fixed to 120. + Cubic + b and c are constrained to a, and alpha, beta and gamma are fixed + to 90. """ from diffpy.structure.spacegroups import GetSpaceGroup, SpaceGroup @@ -140,7 +138,7 @@ def _constrain_as_space_group( from diffpy.structure.symmetryutilities import stdUsymbols if scatterers is None: - scatterers = phase.getScatterers() + scatterers = phase.get_scatterers() if adpsymbols is None: adpsymbols = stdUsymbols @@ -164,38 +162,44 @@ def _constrain_as_space_group( class BaseSpaceGroupParameters(RecipeContainer): """Base class for holding space group Parameters. - This class is used to store the variable Parameters of a structure, leaving - out those that constrained or fixed due to space group. This class has the - same Parameter attribute access of a ParameterSet. The purpose of this - class is to make it easy to access the free variables of a structure for - scripting purposes. + This class stores the variable Parameters of a structure, leaving out + those that are constrained or fixed by the space group. It has the same + Parameter attribute access as a ParameterSet, which makes it easy to + access the free variables of a structure when scripting. Attributes ---------- - name - "sgpars" + name : str + The name of this container (default "sgpars"). """ def __init__(self, name="sgpars"): - """Create the BaseSpaceGroupParameters object. + """Initialize the space group Parameter container. - This initializes the attributes. + Parameters + ---------- + name : str, optional + The name of this container (default "sgpars"). """ RecipeContainer.__init__(self, name) return - def addParameter(self, par, check=True): + def add_parameter(self, par, check=True): """Store a Parameter. Parameters ---------- - par + par : Parameter The Parameter to be stored. - check - If True (default), a ValueError is raised a Parameter of - the specified name has already been inserted. - - Raises ValueError if the Parameter has no name. + check : bool, optional + The flag indicating whether to check for an existing Parameter + of the same name (default True). + + Raises + ------ + ValueError + If the Parameter has no name, or if `check` is True and a + Parameter of the same name has already been stored. """ # Store the Parameter RecipeContainer._add_object(self, par, self._parameters, check) @@ -206,47 +210,39 @@ def addParameter(self, par, check=True): class SpaceGroupParameters(BaseSpaceGroupParameters): - """Class for holding and creating space group Parameters. + """Create and hold the free Parameters of a space group constraint. - This class is used to store the variable Parameters of a structure, leaving - out those that constrained or fixed due to space group. This does the work - of the constrain_as_space_group method. This class has the same Parameter - attribute access of a ParameterSet. + This class stores the variable Parameters of a structure, leaving out + those that are constrained or fixed by the space group, and does the + work of constrain_as_space_group. It has the same Parameter attribute + access as a ParameterSet. Attributes ---------- - name - "sgpars" - phase - The constrained BaseStructure object. - sg - The diffpy.structure.spacegroups.SpaceGroup object - corresponding to the space group. - sgoffset - Optional offset for the space group origin. - scatterers + name : str + The name of this container, always "sgpars". + phase : BaseStructureParSet + The constrained structure ParameterSet. + sg : diffpy.structure.spacegroups.SpaceGroup + The space group of the constraints. + sgoffset : list of float + The offset of the space group origin. + scatterers : list of ParameterSet The constrained scatterer ParameterSets. - constrainlat - Flag indicating whether the lattice is constrained. - constrainadps - Flag indicating whether the ADPs are constrained. - adpsymbols - A list of the ADP names. - _xyzpars - BaseSpaceGroupParameters of free xyz Parameters that are - constrained to. - xyzpars - Property that populates _xyzpars. - _latpars - BaseSpaceGroupParameters of free lattice Parameters that - are constrained to. - latpars - Property that populates _latpars. - _adppars - BaseSpaceGroupParameters of free ADPs that are constrained - to. - adppars - Property that populates _adppars. + constrainlat : bool + The flag indicating whether the lattice is constrained. + constrainadps : bool + The flag indicating whether the ADPs are constrained. + adpsymbols : list of str + The ADP names. + isosymbol : str or None + The name of the isotropic ADP. + xyzpars : BaseSpaceGroupParameters + The free xyz Parameters, created on first access. + latpars : BaseSpaceGroupParameters + The free lattice Parameters, created on first access. + adppars : BaseSpaceGroupParameters + The free ADP Parameters, created on first access. """ def __init__( @@ -260,33 +256,31 @@ def __init__( adpsymbols, isosymbol, ): - """Create the SpaceGroupParameters object. + """Initialize the space group Parameters. + + The constraints are not applied until the Parameters are first + accessed. Parameters ---------- - phase - A BaseStructure object to be constrained. - sg - The space group number or symbol (compatible with - diffpy.structure.spacegroups.GetSpaceGroup. - sgoffset - Optional offset for sg origin. - scatterers - The scatterer ParameterSets to constrain. If scatterers - is None, then all scatterers accessible from - phase.getScatterers will be constrained. - constrainlat - Flag indicating whether to constrain the lattice. - constrainadps - Flag indicating whether to constrain the ADPs. - adpsymbols - A list of the ADP names. The names must be given in the - same order as + phase : BaseStructureParSet + The structure ParameterSet to be constrained. + sg : diffpy.structure.spacegroups.SpaceGroup + The space group of the constraints. + scatterers : list of ParameterSet + The scatterer ParameterSets to constrain. + sgoffset : list of float + The offset of the space group origin. + constrainlat : bool + The flag indicating whether to constrain the lattice. + constrainadps : bool + The flag indicating whether to constrain the ADPs. + adpsymbols : list of str + The ADP names, in the same order as diffpy.structure.symmetryutilities.stdUsymbols. - isosymbol - Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrained via the anisotropic - ADPs. + isosymbol : str or None + The name of the isotropic ADP. If None, isotropic ADPs are + constrained via the anisotropic ADPs. """ BaseSpaceGroupParameters.__init__(self) self._latpars = None @@ -393,7 +387,7 @@ def _clear_constraints(self): # Clear the lattice if self.constrainlat: - lattice = phase.getLattice() + lattice = phase.get_lattice() latpars = [ lattice.a, lattice.b, @@ -434,13 +428,13 @@ def _constrain_lattice(self): phase = self.phase sg = self.sg - lattice = phase.getLattice() + lattice = phase.get_lattice() system = sg.crystal_system if not system: system = "Triclinic" system = system.title() # This makes the constraints - f = _constraintMap[system] + f = _constraint_map[system] f(lattice) # Now get the unconstrained, non-constant lattice pars and store them. @@ -458,7 +452,7 @@ def _constrain_lattice(self): # FIXME - the original parameter will still appear as # constrained. newpar = self.__add_par(par.name, par) - self._latpars.addParameter(newpar) + self._latpars.add_parameter(newpar) return @@ -489,7 +483,7 @@ def _constrain_xyzs(self, positions): idx = int(idx) par = scatterers[idx].get(name) newpar = self.__add_par(pname, par) - self._xyzpars.addParameter(newpar) + self._xyzpars.add_parameter(newpar) # Constrain non-free xyz parameters fpos = g.positionFormulas(xyznames) @@ -574,13 +568,13 @@ def _constrain_adps(self, positions): if par is not None: parname = "%s_%i" % (isosymbol, idx) newpar = self.__add_par(parname, par) - self._adppars.addParameter(newpar) + self._adppars.add_parameter(newpar) isonames.append(newpar.name) else: par = scatterer.get(name) if par is not None: newpar = self.__add_par(pname, par) - self._adppars.addParameter(newpar) + self._adppars.add_parameter(newpar) # Constrain dependent isotropics for idx, isoname in zip(isoidx[:], isonames): @@ -619,7 +613,7 @@ def __add_par(self, parname, par): Index to identify scatterer from which par comes """ newpar = ParameterProxy(parname, par) - self.addParameter(newpar) + self.add_parameter(newpar) return newpar @@ -751,7 +745,7 @@ def _constrain_cubic(lattice): # This is used to map the correct crystal system to the proper constraint # function. -_constraintMap = { +_constraint_map = { "Triclinic": _constrain_triclinic, "Monoclinic": _constrain_monoclinic, "Orthorhombic": _constrain_orthorhombic, diff --git a/src/diffpy/cmistructure/srrealparset.py b/src/diffpy/cmistructure/srrealparset.py index 61c5538..f957a87 100644 --- a/src/diffpy/cmistructure/srrealparset.py +++ b/src/diffpy/cmistructure/srrealparset.py @@ -28,12 +28,11 @@ class SrRealParSet(BaseStructureParSet): Attributes ---------- - stru - The adapted object - _usesymmetry - A flag indicating if SrReal calculators that operate on - this object should use symmetry. By default this is - True. + stru : object + The adapted structure object. + _usesymmetry : bool + The flag indicating if SrReal calculators that operate on + this object should use symmetry (default True). """ def __init__(self, *args, **kw): @@ -42,26 +41,29 @@ def __init__(self, *args, **kw): self.stru = None return - def restrainBVS(self, sig=1, scaled=False): + def restrain_bvs(self, sig=1, scaled=False): """Restrain the bond-valence sum to zero. This adds a penalty to the cost function equal to - bvmsdiff / sig**2 - where bvmsdiff is the mean-squared difference between the calculated - and expected bond valence sums for the structure. If scaled is True, - this is also scaled by the current point-averaged chi^2 value so the - restraint is roughly equally weighted in the fit. + ``bvmsdiff / sig**2``, where ``bvmsdiff`` is the mean-squared + difference between the calculated and expected bond valence sums + for the structure. If `scaled` is True, this is also scaled by the + current point-averaged chi^2 value so the restraint is roughly + equally weighted in the fit. Parameters ---------- - sig + sig : float, optional The uncertainty on the BVS (default 1). - scaled - A flag indicating if the restraint is scaled - (multiplied) by the unrestrained point-average chi^2 - (chi^2/numpoints) (default False). - - Returns the BVSRestraint object for use with the 'unrestrain' method. + scaled : bool, optional + The flag indicating if the restraint is scaled (multiplied) + by the unrestrained point-average chi^2 (chi^2/numpoints) + (default False). + + Returns + ------- + BVSRestraint + The restraint object, for use with the ``unrestrain`` method. """ # Create the Restraint object res = BVSRestraint(self, sig, scaled) @@ -72,17 +74,28 @@ def restrainBVS(self, sig=1, scaled=False): # Return the Restraint object return res - def useSymmetry(self, use=True): - """Set this structure to use symmetry. + def use_symmetry(self, use=True): + """Set whether this structure uses symmetry. This determines how the structure is treated by SrReal calculators. + + Parameters + ---------- + use : bool, optional + The flag indicating if symmetry is used (default True). """ self._usesymmetry = bool(use) return - def usingSymmetry(self): - """Check if symmetry is being used.""" + def using_symmetry(self): + """Return whether symmetry is being used. + + Returns + ------- + bool + The flag indicating if symmetry is used. + """ return self._usesymmetry def _get_srreal_structure(self): diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index f9b409f..d6cdbb2 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -148,7 +148,7 @@ def testImplicitBondAngleRestraints(self): # make our crystal cryst = ObjCrystCrystalParSet("bucky", occryst) m = cryst.c60 - m.wrapRestraints() + m.wrap_restraints() # make sure that we have some restraints in the molecule assert 2 == len(m._restraints) @@ -244,7 +244,7 @@ def testImplicitBondLengthRestraints(self): # make our crystal cryst = ObjCrystCrystalParSet("bucky", occryst) m = cryst.c60 - m.wrapRestraints() + m.wrap_restraints() # make sure that we have some restraints in the molecule assert 2 == len(m._restraints) @@ -274,7 +274,7 @@ def testImplicitDihedralAngleRestraints(self): # make our crystal cryst = ObjCrystCrystalParSet("bucky", occryst) m = cryst.c60 - m.wrapRestraints() + m.wrap_restraints() # make sure that we have some restraints in the molecule assert 2 == len(m._restraints) @@ -303,8 +303,8 @@ def testExplicitBondLengthRestraints(self): m = cryst.c60 # make some bond angle restraints - res0 = m.restrainBondLength(m.atoms[0], m.atoms[5], 3.3, 0.1, 0.1) - res1 = m.restrainBondLength(m.atoms[0], m.atoms[7], 3.3, 0.1, 0.1) + res0 = m.restrain_bond_length(m.atoms[0], m.atoms[5], 3.3, 0.1, 0.1) + res1 = m.restrain_bond_length(m.atoms[0], m.atoms[7], 3.3, 0.1, 0.1) # make sure that we have some restraints in the molecule assert 2 == len(m._restraints) @@ -332,10 +332,10 @@ def testExplicitBondAngleRestraints(self): m = cryst.c60 # restrain some bond angles - res0 = m.restrainBondAngle( + res0 = m.restrain_bond_angle( m.atoms[0], m.atoms[5], m.atoms[8], 3.3, 0.1, 0.1 ) - res1 = m.restrainBondAngle( + res1 = m.restrain_bond_angle( m.atoms[0], m.atoms[7], m.atoms[44], 3.3, 0.1, 0.1 ) @@ -360,10 +360,10 @@ def testExplicitDihedralAngleRestraints(self): m = cryst.c60 # Restrain some dihedral angles. - res0 = m.restrainDihedralAngle( + res0 = m.restrain_dihedral_angle( m.atoms[0], m.atoms[5], m.atoms[8], m.atoms[41], 1.1, 0.1, 0.1 ) - res1 = m.restrainDihedralAngle( + res1 = m.restrain_dihedral_angle( m.atoms[0], m.atoms[7], m.atoms[44], m.atoms[2], 1.1, 0.1, 0.1 ) @@ -391,9 +391,9 @@ def testExplicitBondLengthParameter(self): a20 = m.atoms[20] # Add a parameter - p1 = m.addBondLengthParameter("C07", a0, a7) + p1 = m.add_bond_length_parameter("C07", a0, a7) # Have another atom tag along for the ride - p1.addAtoms([a20]) + p1.add_atoms([a20]) xyz0 = numpy.array( [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] @@ -482,9 +482,9 @@ def testExplicitBondAngleParameter(self): angle0 = numpy.arccos(numpy.dot(v1, v2) / (d1 * d2)) # Add a parameter - p1 = m.addBondAngleParameter("C0720", a0, a7, a20) + p1 = m.add_bond_angle_parameter("C0720", a0, a7, a20) # Have another atom tag along for the ride - p1.addAtoms([a25]) + p1.add_atoms([a25]) assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) @@ -567,9 +567,9 @@ def testExplicitDihedralAngleParameter(self): angle0 = -numpy.arccos(numpy.dot(v123, v234) / (d123 * d234)) # Add a parameter - p1 = m.addDihedralAngleParameter("C072025", a0, a7, a20, a25) + p1 = m.add_dihedral_angle_parameter("C072025", a0, a7, a20, a25) # Have another atom tag along for the ride - p1.addAtoms([a33]) + p1.add_atoms([a33]) assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 3b6fd3f..86ea990 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -46,7 +46,7 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): stru.sgpars.adppars # Check the orthorhombic lattice - lattice = stru.getLattice() + lattice = stru.get_lattice() assert lattice.alpha.const assert lattice.beta.const assert lattice.gamma.const @@ -60,7 +60,7 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): assert 0 == len(lattice._constraints) # Now make sure the scatterers are constrained properly - scatterers = stru.getScatterers() + scatterers = stru.get_scatterers() la = scatterers[0] assert not la.x.const assert not la.y.const @@ -119,7 +119,7 @@ def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): sgpars = constrain_as_space_group( parset, "P b n m", - scatterers=parset.getScatterers()[::2], + scatterers=parset.get_scatterers()[::2], constrainadps=True, ) @@ -129,7 +129,7 @@ def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): assert par.get_value() is not None # Test the unconstrained atoms - for scatterer in parset.getScatterers()[1::2]: + for scatterer in parset.get_scatterers()[1::2]: assert not scatterer.x.const assert not scatterer.y.const assert not scatterer.z.const @@ -155,7 +155,7 @@ def _proxytest(par): def _alltests(par): return _consttest(par) or _constrainedtest(par) or _proxytest(par) - for idx, scatterer in enumerate(parset.getScatterers()[::2]): + for idx, scatterer in enumerate(parset.get_scatterers()[::2]): # Under this scheme, atom 6 is free to vary test = False for par in [scatterer.x, scatterer.y, scatterer.z]: From 82807b1154173232c9a88b2540a436f9c3f15c16 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:26:42 -0400 Subject: [PATCH 05/13] news fix --- news/migrate-structure.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/news/migrate-structure.rst b/news/migrate-structure.rst index 1f69489..de444e5 100644 --- a/news/migrate-structure.rst +++ b/news/migrate-structure.rst @@ -1,6 +1,6 @@ **Added:** -* Add the structure adapters and space group constraints migrated from ``diffpy.srfit.structure``. +* No news added. **Changed:** @@ -16,7 +16,7 @@ **Fixed:** -* Fix the ``diffpy-cmistructure`` entry point to point at ``cmistructure_app``. +* **Security:** From 547af590c48c2d95ef708464b85738f6fbac4478 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:28:03 -0400 Subject: [PATCH 06/13] precommit fixes --- .github/workflows/check-news-item.yml | 2 +- src/diffpy/cmistructure/cmistructure_app.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 20440f6..aa0fc9d 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,7 +3,7 @@ name: Check for News on: pull_request_target: branches: - - main # GitHub does not evaluate expressions in trigger filters; edit this value if your base branch is not main + - main # GitHub does not evaluate expressions in trigger filters; edit this value if your base branch is not main jobs: check-news-item: diff --git a/src/diffpy/cmistructure/cmistructure_app.py b/src/diffpy/cmistructure/cmistructure_app.py index 3bea673..9d2d518 100644 --- a/src/diffpy/cmistructure/cmistructure_app.py +++ b/src/diffpy/cmistructure/cmistructure_app.py @@ -7,7 +7,8 @@ def main(): parser = argparse.ArgumentParser( prog="diffpy.cmistructure", description=( - "diffpy.cmi package for doing refinements with structure objects\n\n" + "diffpy.cmi package for doing refinements with " + "structure objects\n\n" "For more information, visit: " "https://github.com/diffpy/diffpy.cmistructure/" ), From 50a3c7b8c3db3d1cbd2ca9cbea48201d38857cb8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:42:57 -0400 Subject: [PATCH 07/13] refactor: spell out abbreviated parameter, function, and attribute names Replace abbreviations in the public API with full words, e.g. stru_to_parameter_set -> structure_to_parameter_set, stru -> structure, scat -> scatterer, res -> restraint, par -> parameter, cryst -> crystal, idx -> index, sg -> space_group, matoms -> mutated_atoms, angunits -> angle_units, sgpars -> space_group_parameters, and latpars/xyzpars/adppars -> lattice/xyz/adp_parameters. Parameters that have default values (sig, w, sgoffset, constrainlat, adpsymbols, isosymbol, ...) and the default container name "sgpars" are unchanged so keyword-argument callers keep working. Class and module names are unchanged. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/__init__.py | 28 +- .../cmistructure/basestructureparset.py | 10 +- src/diffpy/cmistructure/bvsrestraint.py | 12 +- src/diffpy/cmistructure/cctbxparset.py | 96 ++-- src/diffpy/cmistructure/diffpyparset.py | 38 +- src/diffpy/cmistructure/objcrystparset.py | 418 ++++++++++-------- src/diffpy/cmistructure/sgconstraints.py | 236 +++++----- src/diffpy/cmistructure/srrealparset.py | 14 +- tests/test_diffpyparset.py | 10 +- tests/test_objcrystparset.py | 80 ++-- tests/test_sgconstraints.py | 84 ++-- 11 files changed, 550 insertions(+), 476 deletions(-) diff --git a/src/diffpy/cmistructure/__init__.py b/src/diffpy/cmistructure/__init__.py index b3212f2..22451be 100644 --- a/src/diffpy/cmistructure/__init__.py +++ b/src/diffpy/cmistructure/__init__.py @@ -21,13 +21,13 @@ # package version from diffpy.cmistructure.version import __version__ # noqa -__all__ = ["constrain_as_space_group", "stru_to_parameter_set"] +__all__ = ["constrain_as_space_group", "structure_to_parameter_set"] -def stru_to_parameter_set(name, stru): +def structure_to_parameter_set(name, structure): """Create a ParameterSet adapted to a structure object. - The adapter is chosen from the type of `stru`. Supported types are + The adapter is chosen from the type of `structure`. Supported types are diffpy.structure.Structure, pyobjcryst.crystal.Crystal, pyobjcryst.molecule.Molecule and cctbx.crystal.special_position_settings. @@ -35,38 +35,38 @@ def stru_to_parameter_set(name, stru): ---------- name : str The name to give the structure. - stru : object + structure : object The structure object to adapt. Returns ------- BaseStructureParSet - The ParameterSet adapting `stru`. + The ParameterSet adapting `structure`. Raises ------ TypeError - If `stru` is not one of the supported structure types. + If `structure` is not one of the supported structure types. """ from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet - if DiffpyStructureParSet.can_adapt(stru): - return DiffpyStructureParSet(name, stru) + if DiffpyStructureParSet.can_adapt(structure): + return DiffpyStructureParSet(name, structure) from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet - if ObjCrystCrystalParSet.can_adapt(stru): - return ObjCrystCrystalParSet(name, stru) + if ObjCrystCrystalParSet.can_adapt(structure): + return ObjCrystCrystalParSet(name, structure) from diffpy.cmistructure.objcrystparset import ObjCrystMoleculeParSet - if ObjCrystMoleculeParSet.can_adapt(stru): - return ObjCrystMoleculeParSet(name, stru) + if ObjCrystMoleculeParSet.can_adapt(structure): + return ObjCrystMoleculeParSet(name, structure) from diffpy.cmistructure.cctbxparset import CCTBXCrystalParSet - if CCTBXCrystalParSet.can_adapt(stru): - return CCTBXCrystalParSet(name, stru) + if CCTBXCrystalParSet.can_adapt(structure): + return CCTBXCrystalParSet(name, structure) raise TypeError("Unadaptable structure format") diff --git a/src/diffpy/cmistructure/basestructureparset.py b/src/diffpy/cmistructure/basestructureparset.py index cb681b2..2aaf243 100644 --- a/src/diffpy/cmistructure/basestructureparset.py +++ b/src/diffpy/cmistructure/basestructureparset.py @@ -32,23 +32,23 @@ class BaseStructureParSet(ParameterSet): Attributes ---------- - stru : object + structure : object The adapted structure object. """ @classmethod - def can_adapt(self, stru): + def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- - stru : object + structure : object The structure object to check. Returns ------- bool - The flag indicating if `stru` can be adapted. The base class + The flag indicating if `structure` can be adapted. The base class always returns False. """ return False @@ -60,7 +60,7 @@ def get_lattice(self): lattice Parameters. It is assumed that the lattice parameters are named "a", "b", "c", "alpha", "beta", "gamma". - The lattice must also have the "angunits" attribute, which is + The lattice must also have the "angle_units" attribute, which is either "deg" or "rad", to signify degrees or radians. Returns diff --git a/src/diffpy/cmistructure/bvsrestraint.py b/src/diffpy/cmistructure/bvsrestraint.py index 6ad1ca6..3912a61 100644 --- a/src/diffpy/cmistructure/bvsrestraint.py +++ b/src/diffpy/cmistructure/bvsrestraint.py @@ -34,7 +34,7 @@ class BVSRestraint(Restraint): ---------- _calc : BVSCalculator The SrReal BVSCalculator instance. - _parset : SrRealParSet + _parameter_set : SrRealParSet The SrRealParSet that created this BVSRestraint. sig : float The uncertainty on the BVS (default 1). @@ -44,12 +44,12 @@ class BVSRestraint(Restraint): (default False). """ - def __init__(self, parset, sig=1, scaled=False): + def __init__(self, parameter_set, sig=1, scaled=False): """Initialize the Restraint. Parameters ---------- - parset : SrRealParSet + parameter_set : SrRealParSet The SrRealParSet that creates this BVSRestraint. sig : float, optional The uncertainty on the BVS (default 1). @@ -61,7 +61,7 @@ def __init__(self, parset, sig=1, scaled=False): from diffpy.srreal.bvscalculator import BVSCalculator self._calc = BVSCalculator() - self._parset = parset + self._parameter_set = parameter_set self.sig = float(sig) self.scaled = bool(scaled) return @@ -81,8 +81,8 @@ def penalty(self, w=1.0): The bond-valence penalty. """ # Get the bvms from the BVSCalculator - stru = self._parset._get_srreal_structure() - self._calc.eval(stru) + structure = self._parameter_set._get_srreal_structure() + self._calc.eval(structure) penalty = self._calc.bvmsdiff # Scale by the prefactor diff --git a/src/diffpy/cmistructure/cctbxparset.py b/src/diffpy/cmistructure/cctbxparset.py index 9af3333..9de0803 100644 --- a/src/diffpy/cmistructure/cctbxparset.py +++ b/src/diffpy/cmistructure/cctbxparset.py @@ -53,21 +53,21 @@ class CCTBXScattererParSet(ParameterSet): The isotropic displacement factor of the atom. """ - def __init__(self, name, strups, idx): + def __init__(self, name, structure_parameter_set, index): """Initialize the scatterer ParameterSet. Parameters ---------- name : str The name of this scatterer. - strups : CCTBXCrystalParSet + structure_parameter_set : CCTBXCrystalParSet The CCTBXCrystalParSet that contains the cctbx structure. - idx : int + index : int The index of the scatterer in the structure. """ ParameterSet.__init__(self, name) - self.strups = strups - self.idx = idx + self.structure_parameter_set = structure_parameter_set + self.index = index # x, y, z, occupancy self.add_parameter( @@ -92,36 +92,52 @@ def __init__(self, name, strups, idx): def _xyzgetter(self, i): def f(dummy): - return self.strups.stru.scatterers()[self.idx].site[i] + return self.structure_parameter_set.structure.scatterers()[ + self.index + ].site[i] return f def _xyzsetter(self, i): def f(dummy, value): - xyz = list(self.strups.stru.scatterers()[self.idx].site) + xyz = list( + self.structure_parameter_set.structure.scatterers()[ + self.index + ].site + ) xyz[i] = value - self.strups.stru.scatterers()[self.idx].site = tuple(xyz) + self.structure_parameter_set.structure.scatterers()[ + self.index + ].site = tuple(xyz) return return f def _getocc(self, dummy): - return self.strups.stru.scatterers()[self.idx].occupancy + return self.structure_parameter_set.structure.scatterers()[ + self.index + ].occupancy def _setocc(self, dummy, value): - self.strups.stru.scatterers()[self.idx].occupancy = value + self.structure_parameter_set.structure.scatterers()[ + self.index + ].occupancy = value return def _getuiso(self, dummy): - return self.strups.stru.scatterers()[self.idx].u_iso + return self.structure_parameter_set.structure.scatterers()[ + self.index + ].u_iso def _setuiso(self, dummy, value): - self.strups.stru.scatterers()[self.idx].u_iso = value + self.structure_parameter_set.structure.scatterers()[ + self.index + ].u_iso = value return def _getelem(self): - return self.stru.element_symbol() + return self.structure.element_symbol() element = property(_getelem) @@ -140,18 +156,20 @@ class CCTBXUnitCellParSet(ParameterSet): The unit cell parameters. """ - def __init__(self, strups): + def __init__(self, structure_parameter_set): """Initialize the unit cell ParameterSet. Parameters ---------- - strups : CCTBXCrystalParSet + structure_parameter_set : CCTBXCrystalParSet The CCTBXCrystalParSet that contains the cctbx structure and the unit cell being wrapped. """ ParameterSet.__init__(self, "unitcell") - self.strups = strups - self._latpars = list(self.strups.stru.unit_cell().parameters()) + self.structure_parameter_set = structure_parameter_set + self._lattice_parameters = list( + self.structure_parameter_set.structure.unit_cell().parameters() + ) self.add_parameter( ParameterAdapter("a", None, self._latgetter(0), self._latsetter(0)) @@ -183,15 +201,15 @@ def __init__(self, strups): def _latgetter(self, i): def f(dummy): - return self._latpars[i] + return self._lattice_parameters[i] return f def _latsetter(self, i): def f(dummy, value): - self._latpars[i] = value - self.strups._update = True + self._lattice_parameters[i] = value + self.structure_parameter_set._update = True return return f @@ -207,7 +225,7 @@ class CCTBXCrystalParSet(BaseStructureParSet): Attributes ---------- - stru : cctbx.crystal.special_position_settings + structure : cctbx.crystal.special_position_settings The adapted cctbx structure object. scatterers : list of CCTBXScattererParSet The scatterer ParameterSets. @@ -215,25 +233,25 @@ class CCTBXCrystalParSet(BaseStructureParSet): The unit cell ParameterSet for the structure. """ - def __init__(self, name, stru): + def __init__(self, name, structure): """Initialize the crystal ParameterSet. Parameters ---------- name : str The name of this ParameterSet. - stru : cctbx.crystal.special_position_settings + structure : cctbx.crystal.special_position_settings The cctbx structure to adapt. """ ParameterSet.__init__(self, name) - self.stru = stru + self.structure = structure self.add_parameter_set(CCTBXUnitCellParSet(self)) self.scatterers = [] self._update = False cdict = {} - for s in stru.scatterers(): + for s in structure.scatterers(): el = s.element_symbol() i = cdict.get(el, 0) sname = "%s%i" % (el, i) @@ -260,39 +278,41 @@ def update(self): return self._update = False - stru = self.stru - sgn = stru.space_group().match_tabulated_settings().number() + structure = self.structure + sgn = structure.space_group().match_tabulated_settings().number() # Create the symmetry object from cctbx.crystal import symmetry symm = symmetry( - unit_cell=self.unitcell._latpars, space_group_symbol=sgn + unit_cell=self.unitcell._lattice_parameters, space_group_symbol=sgn ) # Now the new structure - newstru = stru.__class__( - crystal_symmetry=symm, scatterers=stru.scatterers() + newstru = structure.__class__( + crystal_symmetry=symm, scatterers=structure.scatterers() ) - self.unitcell._latpars = list(newstru.unit_cell().parameters()) + self.unitcell._lattice_parameters = list( + newstru.unit_cell().parameters() + ) - self.stru = newstru + self.structure = newstru return @classmethod - def can_adapt(self, stru): + def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- - stru : object + structure : object The structure object to check. Returns ------- bool - The flag indicating if `stru` is a + The flag indicating if `structure` is a cctbx.crystal.special_position_settings. False if cctbx is not installed. """ @@ -300,7 +320,7 @@ def can_adapt(self, stru): from cctbx.crystal import special_position_settings except ImportError: return False - return isinstance(stru, special_position_settings) + return isinstance(structure, special_position_settings) def get_lattice(self): """Return the ParameterSet containing the lattice Parameters. @@ -332,8 +352,8 @@ def get_space_group(self): str The Hermann-Mauguin space group symbol. """ - sg = self.stru.space_group() - t = sg.type() + space_group = self.structure.space_group() + t = space_group.type() return t.lookup_symbol() diff --git a/src/diffpy/cmistructure/diffpyparset.py b/src/diffpy/cmistructure/diffpyparset.py index 73c2487..46918bf 100644 --- a/src/diffpy/cmistructure/diffpyparset.py +++ b/src/diffpy/cmistructure/diffpyparset.py @@ -165,12 +165,12 @@ def _setelem(self, el): # End class DiffpyAtomParSet -def _latgetter(par): - return bind2nd(getattr, par) +def _latgetter(parameter): + return bind2nd(getattr, parameter) -def _latsetter(par): - return bind2nd(setattr, par) +def _latsetter(parameter): + return bind2nd(setattr, parameter) class DiffpyLatticeParSet(ParameterSet): @@ -185,7 +185,7 @@ class DiffpyLatticeParSet(ParameterSet): The lattice this is adapting. name : str The name of this ParameterSet, always "lattice". - angunits : str + angle_units : str The units of the lattice angles, always "deg". a, b, c, alpha, beta, gamma : ParameterAdapter The unit cell parameters. @@ -200,7 +200,7 @@ def __init__(self, lattice): The lattice to adapt. """ ParameterSet.__init__(self, "lattice") - self.angunits = "deg" + self.angle_units = "deg" self.lattice = lattice lat = lattice self.add_parameter( @@ -246,7 +246,7 @@ class DiffpyStructureParSet(SrRealParSet): ---------- atoms : list of DiffpyAtomParSet The atom ParameterSets, provided for convenience. - stru : diffpy.structure.Structure + structure : diffpy.structure.Structure The structure this is adapting. lattice : DiffpyLatticeParSet The managed lattice ParameterSet. @@ -257,23 +257,23 @@ class DiffpyStructureParSet(SrRealParSet): DiffpyAtomParSets are named "Ni0", "Ni1", "Ni2" and "Ni3". """ - def __init__(self, name, stru): + def __init__(self, name, structure): """Initialize the structure ParameterSet. Parameters ---------- name : str The name of the structure. - stru : diffpy.structure.Structure + structure : diffpy.structure.Structure The structure to adapt. """ SrRealParSet.__init__(self, name) - self.stru = stru - self.add_parameter_set(DiffpyLatticeParSet(stru.lattice)) + self.structure = structure + self.add_parameter_set(DiffpyLatticeParSet(structure.lattice)) self.atoms = [] cdict = {} - for a in stru: + for a in structure: el = a.element.title() # Try to sanitize the name. el = el.replace("+", "p") @@ -288,7 +288,7 @@ def __init__(self, name, stru): return def __repr__(self): - return repr(self.stru) + return repr(self.structure) def get_lattice(self): """Return the ParameterSet containing the lattice Parameters. @@ -301,22 +301,22 @@ def get_lattice(self): return self.lattice @classmethod - def can_adapt(self, stru): + def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- - stru : object + structure : object The structure object to check. Returns ------- bool - The flag indicating if `stru` is a diffpy.structure.Structure. + The flag indicating if `structure` is a diffpy.structure.Structure. """ from diffpy.structure import Structure - return isinstance(stru, Structure) + return isinstance(structure, Structure) def get_scatterers(self): """Return the list of ParameterSets that represent the @@ -338,8 +338,8 @@ def _get_srreal_structure(self): """ from diffpy.srreal.structureadapter import nometa - stru = SrRealParSet._get_srreal_structure(self) - return nometa(stru) + structure = SrRealParSet._get_srreal_structure(self) + return nometa(structure) # End class DiffpyStructureParSet diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index 5a19cfc..e348261 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -70,7 +70,7 @@ class for base attributes. Attributes ---------- - scat : pyobjcryst.scatterer.Scatterer + scatterer : pyobjcryst.scatterer.Scatterer The adapted pyobjcryst object. parent : ParameterSet or None The ParameterSet this belongs to. @@ -80,28 +80,28 @@ class for base attributes. The occupancy of the scatterer on its crystal site. """ - def __init__(self, name, scat, parent): + def __init__(self, name, scatterer, parent): """Initialize the scatterer ParameterSet. Parameters ---------- name : str The name of the scatterer. - scat : pyobjcryst.scatterer.Scatterer + scatterer : pyobjcryst.scatterer.Scatterer The pyobjcryst scatterer to adapt. parent : ParameterSet or None The ParameterSet this belongs to. """ ParameterSet.__init__(self, name) - self.scat = scat + self.scatterer = scatterer self.parent = parent # x, y, z, occ - self.add_parameter(ParameterAdapter("x", self.scat, attr="X")) - self.add_parameter(ParameterAdapter("y", self.scat, attr="Y")) - self.add_parameter(ParameterAdapter("z", self.scat, attr="Z")) + self.add_parameter(ParameterAdapter("x", self.scatterer, attr="X")) + self.add_parameter(ParameterAdapter("y", self.scatterer, attr="Y")) + self.add_parameter(ParameterAdapter("z", self.scatterer, attr="Z")) self.add_parameter( - ParameterAdapter("occ", self.scat, attr="Occupancy") + ParameterAdapter("occ", self.scatterer, attr="Occupancy") ) return @@ -138,7 +138,7 @@ class ObjCrystAtomParSet(ObjCrystScattererParSet): Attributes ---------- - scat : pyobjcryst.atom.Atom + scatterer : pyobjcryst.atom.Atom The adapted atom. element : str The non-refinable name of the element (property). @@ -194,7 +194,7 @@ def __init__(self, name, atom, parent): def _getelem(self): """Getter for the element type.""" - return self.scat.GetScatteringPower().GetSymbol() + return self.scatterer.GetScatteringPower().GetSymbol() element = property(_getelem) @@ -211,9 +211,9 @@ class ObjCrystMoleculeParSet(ObjCrystScattererParSet): Attributes ---------- - scat : pyobjcryst.molecule.Molecule + scatterer : pyobjcryst.molecule.Molecule The adapted molecule. - stru : pyobjcryst.molecule.Molecule + structure : pyobjcryst.molecule.Molecule The adapted molecule. parent : ObjCrystCrystalParSet or None The crystal ParameterSet this belongs to. This is None when the @@ -246,13 +246,13 @@ def __init__(self, name, molecule, parent=None): wrapping the molecule. """ ObjCrystScattererParSet.__init__(self, name, molecule, parent) - self.stru = molecule + self.structure = molecule # Add orientation quaternion - self.add_parameter(ParameterAdapter("q0", self.scat, attr="Q0")) - self.add_parameter(ParameterAdapter("q1", self.scat, attr="Q1")) - self.add_parameter(ParameterAdapter("q2", self.scat, attr="Q2")) - self.add_parameter(ParameterAdapter("q3", self.scat, attr="Q3")) + self.add_parameter(ParameterAdapter("q0", self.scatterer, attr="Q0")) + self.add_parameter(ParameterAdapter("q1", self.scatterer, attr="Q1")) + self.add_parameter(ParameterAdapter("q2", self.scatterer, attr="Q2")) + self.add_parameter(ParameterAdapter("q3", self.scatterer, attr="Q3")) # Wrap the MolAtoms within the molecule self.atoms = [] @@ -275,22 +275,22 @@ def __init__(self, name, molecule, parent=None): return @classmethod - def can_adapt(self, stru): + def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- - stru : object + structure : object The structure object to check. Returns ------- bool - The flag indicating if `stru` is a pyobjcryst Molecule. + The flag indicating if `structure` is a pyobjcryst Molecule. """ from pyobjcryst.molecule import Molecule - return isinstance(stru, Molecule) + return isinstance(structure, Molecule) # Part of SrRealParSet interface def use_symmetry(self, use=True): @@ -325,7 +325,7 @@ def _get_srreal_structure(self): Molecule objects are never periodic. Return the object and let the SrReal adapters do the proper thing. """ - return self.stru + return self.structure def get_lattice(self): """Return a ParameterSet holding a unit cubic lattice. @@ -345,7 +345,7 @@ def get_lattice(self): lattice.new_parameter("alpha", 90) lattice.new_parameter("beta", 90) lattice.new_parameter("gamma", 90) - lattice.angunits = "deg" + lattice.angle_units = "deg" return lattice def get_scatterers(self): @@ -368,17 +368,17 @@ def wrap_restraints(self): """ # Wrap restraints. Restraints wrapped in this way cannot be modified # from within this class. - for b in self.scat.GetBondList(): - res = ObjCrystMoleculeRestraint(b) - self._restraints.add(res) + for b in self.scatterer.GetBondList(): + restraint = ObjCrystMoleculeRestraint(b) + self._restraints.add(restraint) - for ba in self.scat.GetBondAngleList(): - res = ObjCrystMoleculeRestraint(ba) - self._restraints.add(res) + for ba in self.scatterer.GetBondAngleList(): + restraint = ObjCrystMoleculeRestraint(ba) + self._restraints.add(restraint) - for da in self.scat.GetDihedralAngleList(): - res = ObjCrystMoleculeRestraint(da) - self._restraints.add(res) + for da in self.scatterer.GetDihedralAngleList(): + restraint = ObjCrystMoleculeRestraint(da) + self._restraints.add(restraint) return @@ -396,7 +396,7 @@ def wrap_stretch_mode_parameters(self): "bl_aname1_aname2" for bond lengths and "ba_aname1_aname2_aname3" for bond angles. """ - for mode in self.scat.GetStretchModeBondLengthList(): + for mode in self.scatterer.GetStretchModeBondLengthList(): name1 = mode.mpAtom0.GetName() name2 = mode.mpAtom1.GetName() @@ -405,18 +405,20 @@ def wrap_stretch_mode_parameters(self): atom1 = getattr(self, name1) atom2 = getattr(self, name2) - par = ObjCrystBondLengthParameter(name, atom1, atom2, mode=mode) + parameter = ObjCrystBondLengthParameter( + name, atom1, atom2, mode=mode + ) atoms = [] for a in mode.GetAtoms(): name = a.GetName() atoms.append(getattr(self, name)) - par.AddAtoms(atoms) + parameter.AddAtoms(atoms) - self.add_parameter(par) + self.add_parameter(parameter) - for mode in self.scat.GetStretchModeBondAngleList(): + for mode in self.scatterer.GetStretchModeBondAngleList(): name1 = mode.mpAtom0.GetName() name2 = mode.mpAtom1.GetName() name3 = mode.mpAtom2.GetName() @@ -427,7 +429,7 @@ def wrap_stretch_mode_parameters(self): atom2 = getattr(self, name2) atom3 = getattr(self, name3) - par = ObjCrystBondAngleParameter( + parameter = ObjCrystBondAngleParameter( name, atom1, atom2, atom3, mode=mode ) @@ -435,9 +437,9 @@ def wrap_stretch_mode_parameters(self): for a in mode.GetAtoms(): name = a.GetName() atoms.append(getattr(self, name)) - par.AddAtoms(atoms) + parameter.AddAtoms(atoms) - self.add_parameter(par) + self.add_parameter(parameter) return @@ -471,24 +473,24 @@ def restrain_bond_length( ObjCrystBondLengthRestraint The restraint, for use with the ``unrestrain`` method. """ - res = ObjCrystBondLengthRestraint( + restraint = ObjCrystBondLengthRestraint( atom1, atom2, length, sigma, delta, scaled ) - self._restraints.add(res) + self._restraints.add(restraint) - return res + return restraint def restrain_bond_length_parameter( - self, par, length, sigma, delta, scaled=False + self, parameter, length, sigma, delta, scaled=False ): """Add a bond length restraint on a bond length Parameter. This creates an ObjCrystBondLengthRestraint between the atoms of - `par` and adds it to the ObjCrystMoleculeParSet. + `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par : ObjCrystBondLengthParameter + parameter : ObjCrystBondLengthParameter The bond length Parameter to restrain (see add_bond_length_parameter). length : float @@ -508,7 +510,7 @@ def restrain_bond_length_parameter( The restraint, for use with the ``unrestrain`` method. """ return self.restrain_bond_length( - par.atom1, par.atom2, length, sigma, delta, scaled + parameter.atom1, parameter.atom2, length, sigma, delta, scaled ) def restrain_bond_angle( @@ -543,24 +545,24 @@ def restrain_bond_angle( ObjCrystBondAngleRestraint The restraint, for use with the ``unrestrain`` method. """ - res = ObjCrystBondAngleRestraint( + restraint = ObjCrystBondAngleRestraint( atom1, atom2, atom3, angle, sigma, delta, scaled ) - self._restraints.add(res) + self._restraints.add(restraint) - return res + return restraint def restrain_bond_angle_parameter( - self, par, angle, sigma, delta, scaled=False + self, parameter, angle, sigma, delta, scaled=False ): """Add a bond angle restraint on a bond angle Parameter. This creates an ObjCrystBondAngleRestraint between the atoms of - `par` and adds it to the ObjCrystMoleculeParSet. + `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par : ObjCrystBondAngleParameter + parameter : ObjCrystBondAngleParameter The bond angle Parameter to restrain (see add_bond_angle_parameter). angle : float @@ -580,7 +582,13 @@ def restrain_bond_angle_parameter( The restraint, for use with the ``unrestrain`` method. """ return self.restrain_bond_angle( - par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled + parameter.atom1, + parameter.atom2, + parameter.atom3, + angle, + sigma, + delta, + scaled, ) def restrain_dihedral_angle( @@ -617,24 +625,24 @@ def restrain_dihedral_angle( ObjCrystDihedralAngleRestraint The restraint, for use with the ``unrestrain`` method. """ - res = ObjCrystDihedralAngleRestraint( + restraint = ObjCrystDihedralAngleRestraint( atom1, atom2, atom3, atom4, angle, sigma, delta, scaled ) - self._restraints.add(res) + self._restraints.add(restraint) - return res + return restraint def restrain_dihedral_angle_parameter( - self, par, angle, sigma, delta, scaled=False + self, parameter, angle, sigma, delta, scaled=False ): """Add a dihedral angle restraint on a dihedral angle Parameter. This creates an ObjCrystDihedralAngleRestraint between the atoms of - `par` and adds it to the ObjCrystMoleculeParSet. + `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- - par : ObjCrystDihedralAngleParameter + parameter : ObjCrystDihedralAngleParameter The dihedral angle Parameter to restrain (see add_dihedral_angle_parameter). angle : float @@ -654,10 +662,10 @@ def restrain_dihedral_angle_parameter( The restraint, for use with the ``unrestrain`` method. """ return self.restrain_dihedral_angle( - par.atom1, - par.atom2, - par.atom3, - par.atom4, + parameter.atom1, + parameter.atom2, + parameter.atom3, + parameter.atom4, angle, sigma, delta, @@ -692,10 +700,12 @@ def add_bond_length_parameter( ObjCrystBondLengthParameter The new bond length Parameter. """ - par = ObjCrystBondLengthParameter(name, atom1, atom2, value, const) - self.add_parameter(par) + parameter = ObjCrystBondLengthParameter( + name, atom1, atom2, value, const + ) + self.add_parameter(parameter) - return par + return parameter def add_bond_angle_parameter( self, name, atom1, atom2, atom3, value=None, const=False @@ -727,12 +737,12 @@ def add_bond_angle_parameter( ObjCrystBondAngleParameter The new bond angle Parameter. """ - par = ObjCrystBondAngleParameter( + parameter = ObjCrystBondAngleParameter( name, atom1, atom2, atom3, value, const ) - self.add_parameter(par) + self.add_parameter(parameter) - return par + return parameter def add_dihedral_angle_parameter( self, name, atom1, atom2, atom3, atom4, value=None, const=False @@ -766,12 +776,12 @@ def add_dihedral_angle_parameter( ObjCrystDihedralAngleParameter The new dihedral angle Parameter. """ - par = ObjCrystDihedralAngleParameter( + parameter = ObjCrystDihedralAngleParameter( name, atom1, atom2, atom3, atom4, value, const ) - self.add_parameter(par) + self.add_parameter(parameter) - return par + return parameter # End class ObjCrystMoleculeParSet @@ -787,7 +797,7 @@ class ObjCrystMolAtomParSet(ObjCrystScattererParSet): Attributes ---------- - scat : pyobjcryst.molecule.MolAtom + scatterer : pyobjcryst.molecule.MolAtom The adapted MolAtom. parent : ObjCrystMoleculeParSet The molecule ParameterSet this belongs to. @@ -805,20 +815,20 @@ class ObjCrystMolAtomParSet(ObjCrystScattererParSet): same. These do not exist for dummy atoms. """ - def __init__(self, name, scat, parent): + def __init__(self, name, scatterer, parent): """Initialize the MolAtom ParameterSet. Parameters ---------- name : str The name of the atom. - scat : pyobjcryst.molecule.MolAtom + scatterer : pyobjcryst.molecule.MolAtom The MolAtom to adapt. parent : ObjCrystMoleculeParSet The molecule ParameterSet this belongs to. """ - ObjCrystScattererParSet.__init__(self, name, scat, parent) - sp = scat.GetScatteringPower() + ObjCrystScattererParSet.__init__(self, name, scatterer, parent) + sp = scatterer.GetScatteringPower() # Only wrap this if there is a scattering power if sp is not None: @@ -843,7 +853,7 @@ def __init__(self, name, scat, parent): def _getelem(self): """Getter for the element type.""" - sp = self.scat.GetScatteringPower() + sp = self.scatterer.GetScatteringPower() if sp: return sp.GetSymbol() else: @@ -859,7 +869,7 @@ def is_dummy(self): bool The flag indicating if this is a dummy atom. """ - return self.scat.IsDummy() + return self.scatterer.IsDummy() # End class ObjCrystMolAtomParSet @@ -875,7 +885,7 @@ class ObjCrystMoleculeRestraint(object): Attributes ---------- - res : object + restraint : object The pyobjcryst Molecule restraint. scaled : bool The flag indicating if the restraint is scaled (multiplied) by @@ -883,19 +893,19 @@ class ObjCrystMoleculeRestraint(object): False). """ - def __init__(self, res, scaled=False): + def __init__(self, restraint, scaled=False): """Wrap a pyobjcryst Molecule restraint as a Restraint. Parameters ---------- - res : object + restraint : object The pyobjcryst Molecule restraint. scaled : bool, optional The flag indicating if the restraint is scaled (multiplied) by the unrestrained point-average chi^2 (chi^2/numpoints) (default False). """ - self.res = res + self.restraint = restraint self.scaled = scaled return @@ -914,7 +924,7 @@ def penalty(self, w=1.0): The log-likelihood of the pyobjcryst restraint, optionally scaled by `w`. """ - penalty = self.res.GetLogLikelihood() + penalty = self.restraint.GetLogLikelihood() if self.scaled: penalty *= w return penalty @@ -938,7 +948,7 @@ class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): The uncertainty of the bond length in Angstroms. delta : float The width of the bond in Angstroms. - res : pyobjcryst.molecule.MolBond + restraint : pyobjcryst.molecule.MolBond The pyobjcryst bond length restraint. scaled : bool The flag indicating if the restraint is scaled (multiplied) by @@ -969,24 +979,26 @@ def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): self.atom1 = atom1 self.atom2 = atom2 - m = self.atom1.scat.GetMolecule() - res = m.AddBond(atom1.scat, atom2.scat, length, sigma, delta) + m = self.atom1.scatterer.GetMolecule() + restraint = m.AddBond( + atom1.scatterer, atom2.scatterer, length, sigma, delta + ) - ObjCrystMoleculeRestraint.__init__(self, res, scaled) + ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint length = property( - lambda self: self.res.GetLength0(), - lambda self, val: self.res.SetLength0(val), + lambda self: self.restraint.GetLength0(), + lambda self, value: self.restraint.SetLength0(value), ) sigma = property( - lambda self: self.res.GetLengthSigma(), - lambda self, val: self.res.SetLengthSigma(val), + lambda self: self.restraint.GetLengthSigma(), + lambda self, value: self.restraint.SetLengthSigma(value), ) delta = property( - lambda self: self.res.GetLengthDelta(), - lambda self, val: self.res.SetLengthDelta(val), + lambda self: self.restraint.GetLengthDelta(), + lambda self, value: self.restraint.SetLengthDelta(value), ) @@ -1010,7 +1022,7 @@ class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): The uncertainty of the bond angle in radians. delta : float The width of the bond angle in radians. - res : pyobjcryst.molecule.MolBondAngle + restraint : pyobjcryst.molecule.MolBondAngle The pyobjcryst bond angle restraint. scaled : bool The flag indicating if the restraint is scaled (multiplied) by @@ -1044,26 +1056,31 @@ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): self.atom2 = atom2 self.atom3 = atom3 - m = self.atom1.scat.GetMolecule() - res = m.AddBondAngle( - atom1.scat, atom2.scat, atom3.scat, angle, sigma, delta + m = self.atom1.scatterer.GetMolecule() + restraint = m.AddBondAngle( + atom1.scatterer, + atom2.scatterer, + atom3.scatterer, + angle, + sigma, + delta, ) - ObjCrystMoleculeRestraint.__init__(self, res, scaled) + ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint angle = property( - lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val), + lambda self: self.restraint.GetAngle0(), + lambda self, value: self.restraint.SetAngle0(value), ) sigma = property( - lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val), + lambda self: self.restraint.GetAngleSigma(), + lambda self, value: self.restraint.SetAngleSigma(value), ) delta = property( - lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val), + lambda self: self.restraint.GetAngleDelta(), + lambda self, value: self.restraint.SetAngleDelta(value), ) @@ -1089,7 +1106,7 @@ class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): The uncertainty of the dihedral angle in radians. delta : float The width of the dihedral angle in radians. - res : pyobjcryst.molecule.MolDihedralAngle + restraint : pyobjcryst.molecule.MolDihedralAngle The pyobjcryst dihedral angle restraint. scaled : bool The flag indicating if the restraint is scaled (multiplied) by @@ -1128,26 +1145,32 @@ def __init__( self.atom3 = atom3 self.atom4 = atom4 - m = self.atom1.scat.GetMolecule() - res = m.AddDihedralAngle( - atom1.scat, atom2.scat, atom3.scat, atom4.scat, angle, sigma, delta + m = self.atom1.scatterer.GetMolecule() + restraint = m.AddDihedralAngle( + atom1.scatterer, + atom2.scatterer, + atom3.scatterer, + atom4.scatterer, + angle, + sigma, + delta, ) - ObjCrystMoleculeRestraint.__init__(self, res, scaled) + ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint angle = property( - lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val), + lambda self: self.restraint.GetAngle0(), + lambda self, value: self.restraint.SetAngle0(value), ) sigma = property( - lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val), + lambda self: self.restraint.GetAngleSigma(), + lambda self, value: self.restraint.SetAngleSigma(value), ) delta = property( - lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val), + lambda self: self.restraint.GetAngleDelta(), + lambda self, value: self.restraint.SetAngleDelta(value), ) @@ -1163,7 +1186,7 @@ class directly. Attributes ---------- - matoms : set of ObjCrystMolAtomParSet + mutated_atoms : set of ObjCrystMolAtomParSet The set of all mutated atoms. Set by the subclass. molecule : ObjCrystMoleculeParSet The molecule the atoms belong to. Set by the subclass. @@ -1198,14 +1221,14 @@ def __init__(self, name, value=None, const=False): Parameter.__init__(self, name, value, const) self.keepcenter = True - def set_value(self, val): + def set_value(self, value): """Set the value of the Parameter by stretching the molecule. The stretch mode moves the mutated atoms by the change in value. Parameters ---------- - val : float + value : float The new value of the Parameter. Returns @@ -1214,17 +1237,17 @@ def set_value(self, val): Return self so that mutators can be chained. """ curval = self.get_value() - val = float(val) + value = float(value) - if val == curval: + if value == curval: return self # The StretchMode expects the change in mutated value. - delta = val - curval + delta = value - curval self.mode.Stretch(delta, self.keepcenter) # Let Parameter take care of the general details - Parameter.set_value(self, val) + Parameter.set_value(self, value) return self @@ -1248,7 +1271,7 @@ def add_atoms(self, atomlist): if not hasattr(atomlist, "__iter__"): atomlist = [atomlist] # Record the added atoms in the Parameter - self.matoms.update(atomlist) + self.mutated_atoms.update(atomlist) # Make sure we're observing these atoms for a in atomlist: a.x.addObserver(self._flush) @@ -1256,7 +1279,7 @@ def add_atoms(self, atomlist): a.z.addObserver(self._flush) # Record the added atoms in the StretchMode - scatlist = [a.scat for a in atomlist] + scatlist = [a.scatterer for a in atomlist] self.mode.AddAtoms(scatlist) return self @@ -1274,7 +1297,7 @@ def notify(self, other=()): """ noneother = () # Notify the atoms that have moved - for a in self.matoms: + for a in self.mutated_atoms: a.x._flush(noneother) a.y._flush(noneother) a.z._flush(noneother) @@ -1324,7 +1347,7 @@ class ObjCrystBondLengthParameter(StretchModeParameter): The first atom in the bond. atom2 : ObjCrystMolAtomParSet The second (mutated) atom in the bond. - matoms : set of ObjCrystMolAtomParSet + mutated_atoms : set of ObjCrystMolAtomParSet The set of all mutated atoms. molecule : ObjCrystMoleculeParSet The molecule the atoms belong to. @@ -1370,10 +1393,12 @@ def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): # Create the mode self.mode = mode if mode is None: - self.mode = StretchModeBondLength(atom1.scat, atom2.scat, None) + self.mode = StretchModeBondLength( + atom1.scatterer, atom2.scatterer, None + ) # We only add the last atom. This is the one that will move - self.mode.AddAtom(atom2.scat) - self.matoms = set([atom2]) + self.mode.AddAtom(atom2.scatterer) + self.mutated_atoms = set([atom2]) # Observe the atom positions for a in [atom1, atom2]: @@ -1387,7 +1412,7 @@ def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): # We do this last so the atoms are defined before we set any values. if value is None: - value = GetBondLength(atom1.scat, atom2.scat) + value = GetBondLength(atom1.scatterer, atom2.scatterer) StretchModeParameter.__init__(self, name, value, const) self.set_constant(const) @@ -1434,8 +1459,8 @@ def get_value(self): The bond length in Angstroms. """ if self._value is None: - val = GetBondLength(self.atom1.scat, self.atom2.scat) - Parameter.set_value(self, val) + value = GetBondLength(self.atom1.scatterer, self.atom2.scatterer) + Parameter.set_value(self, value) return self._value @@ -1463,7 +1488,7 @@ class ObjCrystBondAngleParameter(StretchModeParameter): The second (central) atom in the bond angle. atom3 : ObjCrystMolAtomParSet The third (mutated) atom in the bond angle. - matoms : set of ObjCrystMolAtomParSet + mutated_atoms : set of ObjCrystMolAtomParSet The set of all mutated atoms. molecule : ObjCrystMoleculeParSet The molecule the atoms belong to. @@ -1514,11 +1539,11 @@ def __init__( self.mode = mode if mode is None: self.mode = StretchModeBondAngle( - atom1.scat, atom2.scat, atom3.scat, None + atom1.scatterer, atom2.scatterer, atom3.scatterer, None ) # We only add the last atom. This is the one that will move - self.mode.AddAtom(atom3.scat) - self.matoms = set([atom3]) + self.mode.AddAtom(atom3.scatterer) + self.mutated_atoms = set([atom3]) # Observe the atom positions for a in [atom1, atom2, atom3]: @@ -1533,7 +1558,9 @@ def __init__( # We do this last so the atoms are defined before we set any values. if value is None: - value = GetBondAngle(atom1.scat, atom2.scat, atom3.scat) + value = GetBondAngle( + atom1.scatterer, atom2.scatterer, atom3.scatterer + ) StretchModeParameter.__init__(self, name, value, const) self.set_constant(const) @@ -1579,10 +1606,12 @@ def get_value(self): The bond angle in radians. """ if self._value is None: - val = GetBondAngle( - self.atom1.scat, self.atom2.scat, self.atom3.scat + value = GetBondAngle( + self.atom1.scatterer, + self.atom2.scatterer, + self.atom3.scatterer, ) - Parameter.set_value(self, val) + Parameter.set_value(self, value) return self._value @@ -1612,7 +1641,7 @@ class ObjCrystDihedralAngleParameter(StretchModeParameter): The third (central) atom in the dihedral angle. atom4 : ObjCrystMolAtomParSet The fourth (mutated) atom in the dihedral angle. - matoms : set of ObjCrystMolAtomParSet + mutated_atoms : set of ObjCrystMolAtomParSet The set of all mutated atoms. molecule : ObjCrystMoleculeParSet The molecule the atoms belong to. @@ -1672,10 +1701,12 @@ def __init__( # Create the stretch mode self.mode = mode if mode is None: - self.mode = StretchModeTorsion(atom2.scat, atom3.scat, None) + self.mode = StretchModeTorsion( + atom2.scatterer, atom3.scatterer, None + ) # We only add the last atom. This is the one that will move - self.mode.AddAtom(atom4.scat) - self.matoms = set([atom4]) + self.mode.AddAtom(atom4.scatterer) + self.mutated_atoms = set([atom4]) # Observe the atom positions for a in [atom1, atom2, atom3, atom4]: @@ -1692,7 +1723,10 @@ def __init__( # We do this last so the atoms are defined before we set any values. if value is None: value = GetDihedralAngle( - atom1.scat, atom2.scat, atom3.scat, atom4.scat + atom1.scatterer, + atom2.scatterer, + atom3.scatterer, + atom4.scatterer, ) StretchModeParameter.__init__(self, name, value, const) self.set_constant(const) @@ -1740,13 +1774,13 @@ def get_value(self): The dihedral angle in radians. """ if self._value is None: - val = GetDihedralAngle( - self.atom1.scat, - self.atom2.scat, - self.atom3.scat, - self.atom4.scat, + value = GetDihedralAngle( + self.atom1.scatterer, + self.atom2.scatterer, + self.atom3.scatterer, + self.atom4.scatterer, ) - Parameter.set_value(self, val) + Parameter.set_value(self, value) return self._value @@ -1762,28 +1796,28 @@ class ObjCrystCrystalParSet(SrRealParSet): Attributes ---------- - stru : pyobjcryst.crystal.Crystal + structure : pyobjcryst.crystal.Crystal The adapted crystal. scatterers : list of ObjCrystAtomParSet or ObjCrystMoleculeParSet The scatterer ParameterSets, provided for convenience. - sgpars : SpaceGroupParameters + space_group_parameters : SpaceGroupParameters The free structure Parameters after applying the crystal's space group constraints, created when first accessed. See the diffpy.cmistructure.sgconstraints module. - angunits : str + angle_units : str The units of the lattice angles, always "rad". a, b, c, alpha, beta, gamma : ParameterAdapter The unit cell parameters. """ - def __init__(self, name, cryst): + def __init__(self, name, crystal): """Initialize the crystal ParameterSet. Parameters ---------- name : str The name of this ParameterSet. - cryst : pyobjcryst.crystal.Crystal + crystal : pyobjcryst.crystal.Crystal The crystal to adapt. Raises @@ -1797,24 +1831,30 @@ def __init__(self, name, cryst): nor a Molecule. """ SrRealParSet.__init__(self, name) - self.angunits = "rad" - self.stru = cryst - self._sgpars = None + self.angle_units = "rad" + self.structure = crystal + self._space_group_parameters = None - self.add_parameter(ParameterAdapter("a", self.stru, attr="a")) - self.add_parameter(ParameterAdapter("b", self.stru, attr="b")) - self.add_parameter(ParameterAdapter("c", self.stru, attr="c")) - self.add_parameter(ParameterAdapter("alpha", self.stru, attr="alpha")) - self.add_parameter(ParameterAdapter("beta", self.stru, attr="beta")) - self.add_parameter(ParameterAdapter("gamma", self.stru, attr="gamma")) + self.add_parameter(ParameterAdapter("a", self.structure, attr="a")) + self.add_parameter(ParameterAdapter("b", self.structure, attr="b")) + self.add_parameter(ParameterAdapter("c", self.structure, attr="c")) + self.add_parameter( + ParameterAdapter("alpha", self.structure, attr="alpha") + ) + self.add_parameter( + ParameterAdapter("beta", self.structure, attr="beta") + ) + self.add_parameter( + ParameterAdapter("gamma", self.structure, attr="gamma") + ) # Now we must loop over the scatterers and create parameter sets from # them. self.scatterers = [] snames = [] - for j in range(self.stru.GetNbScatterer()): - s = self.stru.GetScatt(j) + for j in range(self.structure.GetNbScatterer()): + s = self.structure.GetScatt(j) name = s.GetName() if not name: raise ValueError("Each Scatterer must have a name") @@ -1824,23 +1864,23 @@ def __init__(self, name, cryst): # Now create the proper object cname = s.GetClassName() if cname == "Atom": - parset = ObjCrystAtomParSet(name, s, self) + parameter_set = ObjCrystAtomParSet(name, s, self) elif cname == "Molecule": - parset = ObjCrystMoleculeParSet(name, s, self) + parameter_set = ObjCrystMoleculeParSet(name, s, self) else: raise TypeError("Unrecognized scatterer '%s'" % cname) - self.add_parameter_set(parset) - self.scatterers.append(parset) + self.add_parameter_set(parameter_set) + self.scatterers.append(parameter_set) snames.append(name) return def _constrain_space_group(self): """Constrain the space group.""" - if self._sgpars is not None: - return self._sgpars - sg = self._create_space_group(self.stru.GetSpaceGroup()) + if self._space_group_parameters is not None: + return self._space_group_parameters + space_group = self._create_space_group(self.structure.GetSpaceGroup()) from diffpy.cmistructure.sgconstraints import ( _constrain_as_space_group, ) @@ -1848,17 +1888,17 @@ def _constrain_space_group(self): adpsymbols = ["B11", "B22", "B33", "B12", "B13", "B23"] isosymbol = "Biso" sgoffset = [0, 0, 0] - self._sgpars = _constrain_as_space_group( + self._space_group_parameters = _constrain_as_space_group( self, - sg, + space_group, self.scatterers, sgoffset, adpsymbols=adpsymbols, isosymbol=isosymbol, ) - return self._sgpars + return self._space_group_parameters - sgpars = property(_constrain_space_group) + space_group_parameters = property(_constrain_space_group) @staticmethod def _create_space_group(sgobjcryst): @@ -1885,18 +1925,18 @@ def _create_space_group(sgobjcryst): # Get whatever spacegroup we can get by name. This will set the proper # crystal system. Creating a copy of the singleton from GetSpaceGroup, # as this function messes with sg.symop_list. - sg = copy.copy(GetSpaceGroup(name)) + space_group = copy.copy(GetSpaceGroup(name)) # Replace the symmetry operations to guarantee that we get it right. symops = sgobjcryst.GetSymmetryOperations() tranops = sgobjcryst.GetTranslationVectors() - sg.symop_list = [] + space_group.symop_list = [] for trans in tranops: for shift, rot in symops: tv = trans + shift tv -= numpy.floor(tv) - sg.symop_list.append(SymOp(rot, tv)) + space_group.symop_list.append(SymOp(rot, tv)) if sgobjcryst.IsCentrosymmetric(): center = sgobjcryst.GetInversionCenter() @@ -1904,27 +1944,27 @@ def _create_space_group(sgobjcryst): for shift, rot in symops: tv = center - trans - shift tv -= numpy.floor(tv) - sg.symop_list.append(SymOp(-rot, tv)) + space_group.symop_list.append(SymOp(-rot, tv)) - return sg + return space_group @classmethod - def can_adapt(self, stru): + def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- - stru : object + structure : object The structure object to check. Returns ------- bool - The flag indicating if `stru` is a pyobjcryst Crystal. + The flag indicating if `structure` is a pyobjcryst Crystal. """ from pyobjcryst.crystal import Crystal - return isinstance(stru, Crystal) + return isinstance(structure, Crystal) def get_lattice(self): """Return the ParameterSet containing the lattice Parameters. diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index 71b7a06..758062b 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -101,12 +101,12 @@ def constrain_as_space_group( """ from diffpy.structure.spacegroups import GetSpaceGroup, SpaceGroup - sg = spacegroup + space_group = spacegroup if not isinstance(spacegroup, SpaceGroup): - sg = GetSpaceGroup(spacegroup) + space_group = GetSpaceGroup(spacegroup) sgp = _constrain_as_space_group( phase, - sg, + space_group, scatterers, sgoffset, constrainlat, @@ -120,7 +120,7 @@ def constrain_as_space_group( def _constrain_as_space_group( phase, - sg, + space_group, scatterers=None, sgoffset=[0, 0, 0], constrainlat=True, @@ -144,7 +144,7 @@ def _constrain_as_space_group( sgp = SpaceGroupParameters( phase, - sg, + space_group, scatterers, sgoffset, constrainlat, @@ -184,12 +184,12 @@ def __init__(self, name="sgpars"): RecipeContainer.__init__(self, name) return - def add_parameter(self, par, check=True): + def add_parameter(self, parameter, check=True): """Store a Parameter. Parameters ---------- - par : Parameter + parameter : Parameter The Parameter to be stored. check : bool, optional The flag indicating whether to check for an existing Parameter @@ -202,7 +202,7 @@ def add_parameter(self, par, check=True): Parameter of the same name has already been stored. """ # Store the Parameter - RecipeContainer._add_object(self, par, self._parameters, check) + RecipeContainer._add_object(self, parameter, self._parameters, check) return @@ -223,7 +223,7 @@ class SpaceGroupParameters(BaseSpaceGroupParameters): The name of this container, always "sgpars". phase : BaseStructureParSet The constrained structure ParameterSet. - sg : diffpy.structure.spacegroups.SpaceGroup + space_group : diffpy.structure.spacegroups.SpaceGroup The space group of the constraints. sgoffset : list of float The offset of the space group origin. @@ -237,18 +237,18 @@ class SpaceGroupParameters(BaseSpaceGroupParameters): The ADP names. isosymbol : str or None The name of the isotropic ADP. - xyzpars : BaseSpaceGroupParameters + xyz_parameters : BaseSpaceGroupParameters The free xyz Parameters, created on first access. - latpars : BaseSpaceGroupParameters + lattice_parameters : BaseSpaceGroupParameters The free lattice Parameters, created on first access. - adppars : BaseSpaceGroupParameters + adp_parameters : BaseSpaceGroupParameters The free ADP Parameters, created on first access. """ def __init__( self, phase, - sg, + space_group, scatterers, sgoffset, constrainlat, @@ -265,7 +265,7 @@ def __init__( ---------- phase : BaseStructureParSet The structure ParameterSet to be constrained. - sg : diffpy.structure.spacegroups.SpaceGroup + space_group : diffpy.structure.spacegroups.SpaceGroup The space group of the constraints. scatterers : list of ParameterSet The scatterer ParameterSets to constrain. @@ -283,15 +283,15 @@ def __init__( constrained via the anisotropic ADPs. """ BaseSpaceGroupParameters.__init__(self) - self._latpars = None - self._xyzpars = None - self._adppars = None + self._lattice_parameters = None + self._xyz_parameters = None + self._adp_parameters = None self._parsets = {} self._manage(self._parsets) self.phase = phase - self.sg = sg + self.space_group = space_group self.sgoffset = sgoffset self.scatterers = scatterers self.constrainlat = constrainlat @@ -304,44 +304,44 @@ def __init__( def __iter__(self): """Iterate over top-level parameters.""" if ( - self._latpars is None - or self._xyzpars is None - or self._adppars is None + self._lattice_parameters is None + or self._xyz_parameters is None + or self._adp_parameters is None ): self._make_constraints() return RecipeContainer.__iter__(self) - latpars = property(lambda self: self._get_lat_pars()) + lattice_parameters = property(lambda self: self._get_lat_pars()) def _get_lat_pars(self): - """Accessor for _latpars.""" - if self._latpars is None: + """Accessor for _lattice_parameters.""" + if self._lattice_parameters is None: self._constrain_lattice() - return self._latpars + return self._lattice_parameters - xyzpars = property(lambda self: self._get_xyz_pars()) + xyz_parameters = property(lambda self: self._get_xyz_pars()) def _get_xyz_pars(self): - """Accessor for _xyzpars.""" + """Accessor for _xyz_parameters.""" positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) - if self._xyzpars is None: + if self._xyz_parameters is None: self._constrain_xyzs(positions) - return self._xyzpars + return self._xyz_parameters - adppars = property(lambda self: self._get_adp_pars()) + adp_parameters = property(lambda self: self._get_adp_pars()) def _get_adp_pars(self): - """Accessor for _adppars.""" + """Accessor for _adp_parameters.""" positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) - if self._adppars is None: + if self._adp_parameters is None: self._constrain_adps(positions) - return self._adppars + return self._adp_parameters def _make_constraints(self): """Constrain the structure to the space group. @@ -379,16 +379,16 @@ def _clear_constraints(self): # Clear xyz for scatterer in scatterers: - for par in [scatterer.x, scatterer.y, scatterer.z]: - if scatterer.is_constrained(par): - scatterer.remove_constraint(par) - par.set_constant(False) + for parameter in [scatterer.x, scatterer.y, scatterer.z]: + if scatterer.is_constrained(parameter): + scatterer.remove_constraint(parameter) + parameter.set_constant(False) # Clear the lattice if self.constrainlat: lattice = phase.get_lattice() - latpars = [ + lattice_parameters = [ lattice.a, lattice.b, lattice.c, @@ -396,27 +396,27 @@ def _clear_constraints(self): lattice.beta, lattice.gamma, ] - for par in latpars: - if lattice.is_constrained(par): - lattice.remove_constraint(par) - par.set_constant(False) + for parameter in lattice_parameters: + if lattice.is_constrained(parameter): + lattice.remove_constraint(parameter) + parameter.set_constant(False) # Clear ADPs if self.constrainadps: for scatterer in scatterers: if isosymbol: - par = scatterer.get(isosymbol) - if par is not None: - if scatterer.is_constrained(par): - scatterer.remove_constraint(par) - par.set_constant(False) + parameter = scatterer.get(isosymbol) + if parameter is not None: + if scatterer.is_constrained(parameter): + scatterer.remove_constraint(parameter) + parameter.set_constant(False) for pname in adpsymbols: - par = scatterer.get(pname) - if par is not None: - if scatterer.is_constrained(par): - scatterer.remove_constraint(par) - par.set_constant(False) + parameter = scatterer.get(pname) + if parameter is not None: + if scatterer.is_constrained(parameter): + scatterer.remove_constraint(parameter) + parameter.set_constant(False) return @@ -426,10 +426,10 @@ def _constrain_lattice(self): return phase = self.phase - sg = self.sg + space_group = self.space_group lattice = phase.get_lattice() - system = sg.crystal_system + system = space_group.crystal_system if not system: system = "Triclinic" system = system.title() @@ -438,8 +438,10 @@ def _constrain_lattice(self): f(lattice) # Now get the unconstrained, non-constant lattice pars and store them. - self._latpars = BaseSpaceGroupParameters("latpars") - latpars = [ + self._lattice_parameters = BaseSpaceGroupParameters( + "lattice_parameters" + ) + lattice_parameters = [ lattice.a, lattice.b, lattice.c, @@ -447,12 +449,14 @@ def _constrain_lattice(self): lattice.beta, lattice.gamma, ] - pars = [p for p in latpars if not p.const and not p.constrained] - for par in pars: + pars = [ + p for p in lattice_parameters if not p.const and not p.constrained + ] + for parameter in pars: # FIXME - the original parameter will still appear as # constrained. - newpar = self.__add_par(par.name, par) - self._latpars.add_parameter(newpar) + newpar = self.__add_par(parameter.name, parameter) + self._lattice_parameters.add_parameter(newpar) return @@ -466,34 +470,34 @@ def _constrain_xyzs(self, positions): """ from diffpy.structure.symmetryutilities import SymmetryConstraints - sg = self.sg + space_group = self.space_group sgoffset = self.sgoffset # We do this without ADPs here so we can skip much complication. See # the _constrain_adps method for details. - g = SymmetryConstraints(sg, positions, sgoffset=sgoffset) + g = SymmetryConstraints(space_group, positions, sgoffset=sgoffset) scatterers = self.scatterers - self._xyzpars = BaseSpaceGroupParameters("xyzpars") + self._xyz_parameters = BaseSpaceGroupParameters("xyz_parameters") # Make proxies to the free xyz parameters - xyznames = [name[:1] + "_" + name[1:] for name, val in g.pospars] + xyznames = [name[:1] + "_" + name[1:] for name, value in g.pospars] for pname in xyznames: - name, idx = pname.rsplit("_", 1) - idx = int(idx) - par = scatterers[idx].get(name) - newpar = self.__add_par(pname, par) - self._xyzpars.add_parameter(newpar) + name, index = pname.rsplit("_", 1) + index = int(index) + parameter = scatterers[index].get(name) + newpar = self.__add_par(pname, parameter) + self._xyz_parameters.add_parameter(newpar) # Constrain non-free xyz parameters fpos = g.positionFormulas(xyznames) - for idx, tmp in enumerate(zip(scatterers, fpos)): + for index, tmp in enumerate(zip(scatterers, fpos)): scatterer, fp = tmp # Extract the constraint equation from the formula for parname, formula in fp.items(): _makeconstraint( - parname, formula, scatterer, idx, self._parameters + parname, formula, scatterer, index, self._parameters ) return @@ -514,13 +518,13 @@ def _constrain_adps(self, positions): if not self.constrainadps: return - sg = self.sg + space_group = self.space_group sgoffset = self.sgoffset scatterers = self.scatterers isosymbol = self.isosymbol adpsymbols = self.adpsymbols adpmap = dict(zip(stdUsymbols, adpsymbols)) - self._adppars = BaseSpaceGroupParameters("adppars") + self._adp_parameters = BaseSpaceGroupParameters("adp_parameters") # Prepare ADPs. Note that not all scatterers have constrainable ADPs. # For example, MoleculeParSet from objcryststructure does not. We @@ -536,50 +540,54 @@ def _constrain_adps(self, positions): continue Uij = numpy.zeros((3, 3), dtype=float) - for idx, par in enumerate(pars): - i, j = _idxtoij[idx] - Uij[i, j] = Uij[j, i] = par.get_value() + for index, parameter in enumerate(pars): + i, j = _idxtoij[index] + Uij[i, j] = Uij[j, i] = parameter.get_value() Uijs.append(Uij) # Discard any positions for the nonadps positions = list(positions) nonadps.reverse() - [positions.pop(idx) for idx in nonadps] + [positions.pop(index) for index in nonadps] # Now we can create symmetry constraints without having to worry about # the nonadps - g = SymmetryConstraints(sg, positions, Uijs, sgoffset=sgoffset) + g = SymmetryConstraints( + space_group, positions, Uijs, sgoffset=sgoffset + ) - adpnames = [adpmap[name[:3]] + "_" + name[3:] for name, val in g.Upars] + adpnames = [ + adpmap[name[:3]] + "_" + name[3:] for name, value in g.Upars + ] # Make proxies to the free adp parameters. We start by filtering out # the isotropic ones so we can use the isotropic parameter. isoidx = [] isonames = [] for pname in adpnames: - name, idx = pname.rsplit("_", 1) - idx = int(idx) + name, index = pname.rsplit("_", 1) + index = int(index) # Check for isotropic ADPs - scatterer = scatterers[idx] - if isosymbol and g.Uisotropy[idx] and idx not in isoidx: - isoidx.append(idx) - par = scatterer.get(isosymbol) - if par is not None: - parname = "%s_%i" % (isosymbol, idx) - newpar = self.__add_par(parname, par) - self._adppars.add_parameter(newpar) + scatterer = scatterers[index] + if isosymbol and g.Uisotropy[index] and index not in isoidx: + isoidx.append(index) + parameter = scatterer.get(isosymbol) + if parameter is not None: + parname = "%s_%i" % (isosymbol, index) + newpar = self.__add_par(parname, parameter) + self._adp_parameters.add_parameter(newpar) isonames.append(newpar.name) else: - par = scatterer.get(name) - if par is not None: - newpar = self.__add_par(pname, par) - self._adppars.add_parameter(newpar) + parameter = scatterer.get(name) + if parameter is not None: + newpar = self.__add_par(pname, parameter) + self._adp_parameters.add_parameter(newpar) # Constrain dependent isotropics - for idx, isoname in zip(isoidx[:], isonames): - for j in g.coremap[idx]: - if j == idx: + for index, isoname in zip(isoidx[:], isonames): + for j in g.coremap[index]: + if j == index: continue isoidx.append(j) scatterer = scatterers[j] @@ -591,18 +599,18 @@ def _constrain_adps(self, positions): # Constrain dependent anisotropics. We use the fact that an # anisotropic cannot be dependent on an isotropic. - for idx, tmp in enumerate(zip(scatterers, fadp)): - if idx in isoidx: + for index, tmp in enumerate(zip(scatterers, fadp)): + if index in isoidx: continue scatterer, fa = tmp # Extract the constraint equation from the formula for stdparname, formula in fa.items(): pname = adpmap[stdparname] _makeconstraint( - pname, formula, scatterer, idx, self._parameters + pname, formula, scatterer, index, self._parameters ) - def __add_par(self, parname, par): + def __add_par(self, parname, parameter): """Constrain a parameter via proxy with a specified name. Parameters @@ -612,7 +620,7 @@ def __add_par(self, parname, par): idx Index to identify scatterer from which par comes """ - newpar = ParameterProxy(parname, par) + newpar = ParameterProxy(parname, parameter) self.add_parameter(newpar) return newpar @@ -636,7 +644,7 @@ def _constrain_monoclinic(lattice): gamma, in which case alpha and gamma are constrained to 90. """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.set_constant(True, ang90) @@ -656,7 +664,7 @@ def _constrain_orthorhombic(lattice): alpha, beta and gamma are constrained to 90 """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.set_constant(True, ang90) @@ -672,7 +680,7 @@ def _constrain_tetragonal(lattice): 90. """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.set_constant(True, ang90) @@ -690,7 +698,7 @@ def _constrain_trigonal(lattice): c are constrained to a, beta and gamma are constrained to alpha. """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor @@ -714,7 +722,7 @@ def _constrain_hexagonal(lattice): gamma is constrained to 120. """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor @@ -732,7 +740,7 @@ def _constrain_cubic(lattice): to 90. """ afactor = 1 - if lattice.angunits == "rad": + if lattice.angle_units == "rad": afactor = deg2rad ang90 = 90.0 * afactor lattice.add_constraint(lattice.b, lattice.a) @@ -756,7 +764,7 @@ def _constrain_cubic(lattice): } -def _makeconstraint(parname, formula, scatterer, idx, ns={}): +def _makeconstraint(parname, formula, scatterer, index, ns={}): """Constrain a parameter according to a formula. Parameters @@ -777,28 +785,28 @@ def _makeconstraint(parname, formula, scatterer, idx, ns={}): par Returns the parameter if it is free. """ - par = scatterer.get(parname) + parameter = scatterer.get(parname) - if par is None: + if parameter is None: return - compname = "%s_%i" % (parname, idx) + compname = "%s_%i" % (parname, index) # Check to see if this parameter is free pat = r"%s *([+-] *\d+)?$" % compname if re.match(pat, formula): - return par + return parameter # Check to see if it is a constant fval = _get_float(formula) if fval is not None: - par.set_constant() + parameter.set_constant() return # If we got here, then we have a constraint equation # Fix any division issues formula = formula.replace("/", "*1.0/") - scatterer.add_constraint(par, formula, params=ns) + scatterer.add_constraint(parameter, formula, params=ns) return diff --git a/src/diffpy/cmistructure/srrealparset.py b/src/diffpy/cmistructure/srrealparset.py index f957a87..7a3f897 100644 --- a/src/diffpy/cmistructure/srrealparset.py +++ b/src/diffpy/cmistructure/srrealparset.py @@ -28,7 +28,7 @@ class SrRealParSet(BaseStructureParSet): Attributes ---------- - stru : object + structure : object The adapted structure object. _usesymmetry : bool The flag indicating if SrReal calculators that operate on @@ -38,7 +38,7 @@ class SrRealParSet(BaseStructureParSet): def __init__(self, *args, **kw): BaseStructureParSet.__init__(self, *args, **kw) self._usesymmetry = True - self.stru = None + self.structure = None return def restrain_bvs(self, sig=1, scaled=False): @@ -66,13 +66,13 @@ def restrain_bvs(self, sig=1, scaled=False): The restraint object, for use with the ``unrestrain`` method. """ # Create the Restraint object - res = BVSRestraint(self, sig, scaled) + restraint = BVSRestraint(self, sig, scaled) # Add it to the _restraints set - self._restraints.add(res) + self._restraints.add(restraint) # Our configuration changed. Notify observers. self._update_configuration() # Return the Restraint object - return res + return restraint def use_symmetry(self, use=True): """Set whether this structure uses symmetry. @@ -107,5 +107,5 @@ def _get_srreal_structure(self): from diffpy.srreal.structureadapter import nosymmetry if self._usesymmetry: - return self.stru - return nosymmetry(self.stru) + return self.structure + return nosymmetry(self.structure) diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index e582175..c579b8c 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -106,9 +106,9 @@ def test___repr__(): lat = Lattice(3, 3, 2, 90, 90, 90) atom = Atom("C", [0, 0.2, 0.5]) - stru = Structure([atom], lattice=lat) - dsps = DiffpyStructureParSet("dsps", stru) - assert repr(stru) == repr(dsps) + structure = Structure([atom], lattice=lat) + dsps = DiffpyStructureParSet("dsps", structure) + assert repr(structure) == repr(dsps) assert repr(lat) == repr(dsps.lattice) assert repr(atom) == repr(dsps.atoms[0]) return @@ -118,8 +118,8 @@ def test_pickling(): """Test pickling of DiffpyStructureParSet.""" from diffpy.structure import Atom, Structure - stru = Structure([Atom("C", [0, 0.2, 0.5])]) - dsps = DiffpyStructureParSet("dsps", stru) + structure = Structure([Atom("C", [0, 0.2, 0.5])]) + dsps = DiffpyStructureParSet("dsps", structure) data = pickle.dumps(dsps) dsps2 = pickle.loads(data) assert 1 == len(dsps2.atoms) diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index d6cdbb2..c340d0c 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -146,8 +146,8 @@ def testImplicitBondAngleRestraints(self): ocmol.AddBondAngle(ocmol[0], ocmol[7], ocmol[44], 1.3, 0.1, 0.1) # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 m.wrap_restraints() # make sure that we have some restraints in the molecule @@ -166,19 +166,19 @@ def testObjCrystParSet(self): """Test the structure conversion.""" occryst = self.occryst ocmol = self.ocmol - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 - assert cryst.name == "bucky" + assert crystal.name == "bucky" def _testCrystal(): # Test the lattice - assert occryst.a == pytest.approx(cryst.a.value) - assert occryst.b == pytest.approx(cryst.b.get_value()) - assert occryst.c == pytest.approx(cryst.c.get_value()) - assert occryst.alpha == pytest.approx(cryst.alpha.get_value()) - assert occryst.beta == pytest.approx(cryst.beta.get_value()) - assert occryst.gamma == pytest.approx(cryst.gamma.get_value()) + assert occryst.a == pytest.approx(crystal.a.value) + assert occryst.b == pytest.approx(crystal.b.get_value()) + assert occryst.c == pytest.approx(crystal.c.get_value()) + assert occryst.alpha == pytest.approx(crystal.alpha.get_value()) + assert occryst.beta == pytest.approx(crystal.beta.get_value()) + assert occryst.gamma == pytest.approx(crystal.gamma.get_value()) return def _testMolecule(): @@ -222,11 +222,11 @@ def _testMolecule(): _testMolecule() # Now change values from the srfit StructureParSet - cryst.c60.C44.x.set_value(1.1) - cryst.c60.C44.occ.set_value(1.1) - cryst.c60.C44.Biso.set_value(1.1) - cryst.c60.q3.set_value(1.1) - cryst.a.set_value(1.1) + crystal.c60.C44.x.set_value(1.1) + crystal.c60.C44.occ.set_value(1.1) + crystal.c60.C44.Biso.set_value(1.1) + crystal.c60.q3.set_value(1.1) + crystal.a.set_value(1.1) _testCrystal() _testMolecule() @@ -242,8 +242,8 @@ def testImplicitBondLengthRestraints(self): ocmol.AddBond(ocmol[0], ocmol[7], 3.3, 0.1, 0.1) # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 m.wrap_restraints() # make sure that we have some restraints in the molecule @@ -272,8 +272,8 @@ def testImplicitDihedralAngleRestraints(self): ) # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 m.wrap_restraints() # make sure that we have some restraints in the molecule @@ -299,8 +299,8 @@ def testExplicitBondLengthRestraints(self): ocmol = self.ocmol # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 # make some bond angle restraints res0 = m.restrain_bond_length(m.atoms[0], m.atoms[5], 3.3, 0.1, 0.1) @@ -328,8 +328,8 @@ def testExplicitBondAngleRestraints(self): ocmol = self.ocmol # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 # restrain some bond angles res0 = m.restrain_bond_angle( @@ -356,8 +356,8 @@ def testExplicitDihedralAngleRestraints(self): ocmol = self.ocmol # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 # Restrain some dihedral angles. res0 = m.restrain_dihedral_angle( @@ -383,8 +383,8 @@ def testExplicitBondLengthParameter(self): occryst = self.occryst # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 a0 = m.atoms[0] a7 = m.atoms[7] @@ -453,8 +453,8 @@ def testExplicitBondAngleParameter(self): occryst = self.occryst # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 a0 = m.atoms[0] a7 = m.atoms[7] @@ -531,8 +531,8 @@ def testExplicitDihedralAngleParameter(self): occryst = self.occryst # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 + crystal = ObjCrystCrystalParSet("bucky", occryst) + m = crystal.c60 a0 = m.atoms[0] a7 = m.atoms[7] @@ -638,17 +638,19 @@ def setup(self, diffpy_structure_available, pyobjcryst_available): from diffpy.structure import spacegroups @staticmethod - def getObjCrystParSetSpaceGroup(sg): + def getObjCrystParSetSpaceGroup(space_group): """Make an ObjCrystCrystalParSet with the proper space group.""" from pyobjcryst.spacegroup import SpaceGroup - sgobjcryst = SpaceGroup(sg.short_name) + sgobjcryst = SpaceGroup(space_group.short_name) sgnew = ObjCrystCrystalParSet._create_space_group(sgobjcryst) return sgnew @staticmethod - def hashDiffPySpaceGroup(sg): - lines = [str(sg.number % 1000)] + sorted(map(str, sg.iter_symops())) + def hashDiffPySpaceGroup(space_group): + lines = [str(space_group.number % 1000)] + sorted( + map(str, space_group.iter_symops()) + ) s = "\n".join(lines) return s @@ -672,10 +674,10 @@ def xtestCreateSpaceGroup(self): shn = smbls.hermann_mauguin() short_name = shn.replace(" ", "") if spacegroups.IsSpaceGroupIdentifier(short_name): - sg = spacegroups.GetSpaceGroup(shn) - sgnew = self.getObjCrystParSetSpaceGroup(sg) + space_group = spacegroups.GetSpaceGroup(shn) + sgnew = self.getObjCrystParSetSpaceGroup(space_group) # print("dbsg: " + repr(self.sgsEquivalent(sg, sgnew))) - assert self.sgsEquivalent(sg, sgnew) + assert self.sgsEquivalent(space_group, sgnew) return diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 86ea990..0e96f4b 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -37,16 +37,16 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): pi = numpy.pi occryst = makeLaMnO3() - stru = ObjCrystCrystalParSet(occryst.GetName(), occryst) + structure = ObjCrystCrystalParSet(occryst.GetName(), occryst) # Make sure we actually create the constraints - stru._constrain_space_group() + structure._constrain_space_group() # Make the space group parameters individually - stru.sgpars.latpars - stru.sgpars.xyzpars - stru.sgpars.adppars + structure.space_group_parameters.lattice_parameters + structure.space_group_parameters.xyz_parameters + structure.space_group_parameters.adp_parameters # Check the orthorhombic lattice - lattice = stru.get_lattice() + lattice = structure.get_lattice() assert lattice.alpha.const assert lattice.beta.const assert lattice.gamma.const @@ -60,7 +60,7 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): assert 0 == len(lattice._constraints) # Now make sure the scatterers are constrained properly - scatterers = stru.get_scatterers() + scatterers = structure.get_scatterers() la = scatterers[0] assert not la.x.const assert not la.y.const @@ -113,23 +113,23 @@ def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet from diffpy.cmistructure.sgconstraints import constrain_as_space_group - stru = makeLaMnO3_P1(datafile) - parset = DiffpyStructureParSet("LaMnO3", stru) + structure = makeLaMnO3_P1(datafile) + parameter_set = DiffpyStructureParSet("LaMnO3", structure) - sgpars = constrain_as_space_group( - parset, + space_group_parameters = constrain_as_space_group( + parameter_set, "P b n m", - scatterers=parset.get_scatterers()[::2], + scatterers=parameter_set.get_scatterers()[::2], constrainadps=True, ) # Make sure that the new parameters were created - for par in sgpars: - assert par is not None - assert par.get_value() is not None + for parameter in space_group_parameters: + assert parameter is not None + assert parameter.get_value() is not None # Test the unconstrained atoms - for scatterer in parset.get_scatterers()[1::2]: + for scatterer in parameter_set.get_scatterers()[1::2]: assert not scatterer.x.const assert not scatterer.y.const assert not scatterer.z.const @@ -141,29 +141,33 @@ def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): assert not scatterer.U23.const assert 0 == len(scatterer._constraints) - proxied = [p.par for p in sgpars] + proxied = [p.par for p in space_group_parameters] - def _consttest(par): - return par.const + def _consttest(parameter): + return parameter.const - def _constrainedtest(par): - return par.constrained + def _constrainedtest(parameter): + return parameter.constrained - def _proxytest(par): - return par in proxied + def _proxytest(parameter): + return parameter in proxied - def _alltests(par): - return _consttest(par) or _constrainedtest(par) or _proxytest(par) + def _alltests(parameter): + return ( + _consttest(parameter) + or _constrainedtest(parameter) + or _proxytest(parameter) + ) - for idx, scatterer in enumerate(parset.get_scatterers()[::2]): + for index, scatterer in enumerate(parameter_set.get_scatterers()[::2]): # Under this scheme, atom 6 is free to vary test = False - for par in [scatterer.x, scatterer.y, scatterer.z]: - test |= _alltests(par) + for parameter in [scatterer.x, scatterer.y, scatterer.z]: + test |= _alltests(parameter) assert test test = False - for par in [ + for parameter in [ scatterer.U11, scatterer.U22, scatterer.U33, @@ -171,7 +175,7 @@ def _alltests(par): scatterer.U13, scatterer.U23, ]: - test |= _alltests(par) + test |= _alltests(parameter) assert test @@ -188,24 +192,24 @@ def test_constrain_as_space_group_args(pyobjcryst_available, datafile): from diffpy.cmistructure.sgconstraints import constrain_as_space_group from diffpy.structure.spacegroups import GetSpaceGroup - stru = makeLaMnO3_P1(datafile) - parset = DiffpyStructureParSet("LaMnO3", stru) - sgpars = constrain_as_space_group(parset, "P b n m") - sg = GetSpaceGroup("P b n m") + structure = makeLaMnO3_P1(datafile) + parameter_set = DiffpyStructureParSet("LaMnO3", structure) + space_group_parameters = constrain_as_space_group(parameter_set, "P b n m") + space_group = GetSpaceGroup("P b n m") parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) - sgpars2 = constrain_as_space_group(parset2, sg) - list(sgpars) + sgpars2 = constrain_as_space_group(parset2, space_group) + list(space_group_parameters) list(sgpars2) - assert sgpars.names == sgpars2.names + assert space_group_parameters.names == sgpars2.names return def makeLaMnO3_P1(datafile): from diffpy.structure import Structure - stru = Structure() - stru.read(datafile("LaMnO3.stru")) - return stru + structure = Structure() + structure.read(datafile("LaMnO3.stru")) + return structure def makeLaMnO3(): From 7b730f88b2c0dd7773db043ad754c7e4b8d978ce Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:43:30 -0400 Subject: [PATCH 08/13] test: remove package-availability skips pyobjcryst and diffpy.structure are declared test and runtime dependencies, so the tests should fail rather than silently skip when they are missing. Remove the pyobjcryst_available and diffpy_structure_available fixtures, their skip checks, and the cctbx import guard in the already-disabled xtestCreateSpaceGroup. Co-Authored-By: Claude Opus 5.5 --- tests/conftest.py | 42 ------------------------------------ tests/test_objcrystparset.py | 17 +++------------ tests/test_sgconstraints.py | 15 +++---------- 3 files changed, 6 insertions(+), 68 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b3398ed..8b363f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,9 @@ import importlib.resources import json -import logging -from functools import lru_cache from pathlib import Path import pytest -logger = logging.getLogger(__name__) - @pytest.fixture def user_filesystem(tmp_path): @@ -24,44 +20,6 @@ def user_filesystem(tmp_path): yield tmp_path -# diffpy.structure -@lru_cache() -def has_diffpy_structure(): - try: - import diffpy.structure as m - - del m - return True - except ImportError: - logger.warning( - "Cannot import diffpy.structure, Structure tests skipped." - ) - return False - - -# pyobjcryst -@lru_cache() -def has_pyobjcryst(): - try: - import pyobjcryst as m - - del m - return True - except ImportError: - logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") - return False - - -@pytest.fixture(scope="session") -def diffpy_structure_available(): - return has_diffpy_structure() - - -@pytest.fixture(scope="session") -def pyobjcryst_available(): - return has_pyobjcryst() - - @pytest.fixture(scope="session") def datafile(): """Fixture to load a test data file from the testdata package diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index c340d0c..d8e295f 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -113,11 +113,8 @@ def makeC60(): class TestParameterAdapter: @pytest.fixture(autouse=True) - def setup(self, pyobjcryst_available): + def setup(self): # shared setup - if not pyobjcryst_available: - pytest.skip("pyobjcryst package not available") - global ObjCrystCrystalParSet, Crystal, Atom, Molecule global ScatteringPowerAtom from pyobjcryst.atom import Atom @@ -626,13 +623,8 @@ class TestCreateSpaceGroup: """ @pytest.fixture(autouse=True) - def setup(self, diffpy_structure_available, pyobjcryst_available): + def setup(self): # shared setup - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - if not pyobjcryst_available: - pytest.skip("pyobjcryst package not available") - global ObjCrystCrystalParSet, spacegroups from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet from diffpy.structure import spacegroups @@ -665,10 +657,7 @@ def sgsEquivalent(self, sg1, sg2): def xtestCreateSpaceGroup(self): """Check all sgtbx space groups for proper conversion to SpaceGroup.""" - try: - from cctbx import sgtbx - except ImportError: - return + from cctbx import sgtbx for smbls in sgtbx.space_group_symbol_iterator(): shn = smbls.hermann_mauguin() diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 0e96f4b..f4440f9 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -22,16 +22,13 @@ # ---------------------------------------------------------------------------- -def test_ObjCryst_constrain_space_group(pyobjcryst_available): +def test_ObjCryst_constrain_space_group(): """Make sure that all Parameters are constrained properly. This tests constrainSpaceGroup from diffpy.cmistructure.sgconstraints, which is performed automatically when an ObjCrystCrystalParSet is created. """ - if not pyobjcryst_available: - pytest.skip("pyobjcrysta package not available") - from diffpy.cmistructure.objcrystparset import ObjCrystCrystalParSet pi = numpy.pi @@ -105,11 +102,8 @@ def test_ObjCryst_constrain_space_group(pyobjcryst_available): return -def test_DiffPy_constrain_as_space_group(datafile, pyobjcryst_available): +def test_DiffPy_constrain_as_space_group(datafile): """Test the constrain_as_space_group function.""" - if not pyobjcryst_available: - pytest.skip("pyobjcrysta package not available") - from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet from diffpy.cmistructure.sgconstraints import constrain_as_space_group @@ -182,12 +176,9 @@ def _alltests(parameter): return -def test_constrain_as_space_group_args(pyobjcryst_available, datafile): +def test_constrain_as_space_group_args(datafile): """Test the arguments processing of constrain_as_space_group function.""" - if not pyobjcryst_available: - pytest.skip("pyobjcrysta package not available") - from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet from diffpy.cmistructure.sgconstraints import constrain_as_space_group from diffpy.structure.spacegroups import GetSpaceGroup From b179b90474c5ba92f3703bbe9a61aca7058ec5ba Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:47:12 -0400 Subject: [PATCH 09/13] test: use actual/expected assertions with case comments Rewrite the assertions in test_diffpyparset, test_objcrystparset and test_sgconstraints as `assert actual_* == expected_*`, with the code under test on the actual side, and add C#/Expected comments for each logical stage. Related checks (e.g. which coordinates are fixed, which atoms moved) are compared as one dict or list so a failure reports every mismatch at once. What each test checks, including tolerances, is unchanged. Co-Authored-By: Claude Opus 5.5 --- tests/test_diffpyparset.py | 117 ++++++++---- tests/test_objcrystparset.py | 343 ++++++++++++++++++++++++----------- tests/test_sgconstraints.py | 212 +++++++++++++--------- 3 files changed, 445 insertions(+), 227 deletions(-) diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index c579b8c..0d3b9f7 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -37,48 +37,78 @@ def testDiffpyStructureParSet(): s = DiffpyStructureParSet("CuAg", dsstru) - assert s.name == "CuAg" + actual_name = s.name + expected_name = "CuAg" + assert actual_name == expected_name def _testAtoms(): # Check the atoms thoroughly - assert a1.element == s.Cu0.element - assert a2.element == s.Ag0.element - assert a1.Uisoequiv == s.Cu0.Uiso.get_value() - assert a2.Uisoequiv == s.Ag0.Uiso.get_value() - assert a1.Bisoequiv == s.Cu0.Biso.get_value() - assert a2.Bisoequiv == s.Ag0.Biso.get_value() + actual_atoms = { + "Cu0": { + "element": s.Cu0.element, + "Uiso": s.Cu0.Uiso.get_value(), + "Biso": s.Cu0.Biso.get_value(), + "xyz": [ + s.Cu0.x.get_value(), + s.Cu0.y.get_value(), + s.Cu0.z.get_value(), + ], + }, + "Ag0": { + "element": s.Ag0.element, + "Uiso": s.Ag0.Uiso.get_value(), + "Biso": s.Ag0.Biso.get_value(), + }, + } + expected_atoms = { + "Cu0": { + "element": a1.element, + "Uiso": a1.Uisoequiv, + "Biso": a1.Bisoequiv, + "xyz": [a1.xyz[0], a1.xyz[1], a1.xyz[2]], + }, + "Ag0": { + "element": a2.element, + "Uiso": a2.Uisoequiv, + "Biso": a2.Bisoequiv, + }, + } + assert actual_atoms == expected_atoms + + # The Uij and Uji (Bij and Bji) Parameters both read the + # structure's Uij (Bij). + actual_anisotropic = {} + expected_anisotropic = {} for i in range(1, 4): for j in range(i, 4): - uijstru = getattr(a1, "U%i%i" % (i, j)) - uij = getattr(s.Cu0, "U%i%i" % (i, j)).get_value() - uji = getattr(s.Cu0, "U%i%i" % (j, i)).get_value() - assert uijstru == uij - assert uijstru == uji - bijstru = getattr(a1, "B%i%i" % (i, j)) - bij = getattr(s.Cu0, "B%i%i" % (i, j)).get_value() - bji = getattr(s.Cu0, "B%i%i" % (j, i)).get_value() - assert bijstru == bij - assert bijstru == bji - - assert a1.xyz[0] == s.Cu0.x.get_value() - assert a1.xyz[1] == s.Cu0.y.get_value() - assert a1.xyz[2] == s.Cu0.z.get_value() + for prefix in "UB": + ij = "%s%i%i" % (prefix, i, j) + ji = "%s%i%i" % (prefix, j, i) + actual_anisotropic[ij] = getattr(s.Cu0, ij).get_value() + actual_anisotropic[ji] = getattr(s.Cu0, ji).get_value() + expected_anisotropic[ij] = getattr(a1, ij) + expected_anisotropic[ji] = getattr(a1, ij) + assert actual_anisotropic == expected_anisotropic return def _testLattice(): - # Test the lattice - assert dsstru.lattice.a == s.lattice.a.get_value() - assert dsstru.lattice.b == s.lattice.b.get_value() - assert dsstru.lattice.c == s.lattice.c.get_value() - assert dsstru.lattice.alpha == s.lattice.alpha.get_value() - assert dsstru.lattice.beta == s.lattice.beta.get_value() - assert dsstru.lattice.gamma == s.lattice.gamma.get_value() - + lattice_names = ["a", "b", "c", "alpha", "beta", "gamma"] + actual_lattice = [ + getattr(s.lattice, name).get_value() for name in lattice_names + ] + expected_lattice = [ + getattr(dsstru.lattice, name) for name in lattice_names + ] + assert actual_lattice == expected_lattice + + # C1: The ParameterSet has just been created from the structure. + # Expected: The Parameters match the atoms and lattice. _testAtoms() _testLattice() - # Now change some values from the diffpy Structure + # C2: The diffpy Structure is changed directly. + # Expected: The Parameters follow the changes. a1.xyz[1] = 0.123 a1.U11 = 0.321 a1.B32 = 0.111 @@ -86,7 +116,9 @@ def _testLattice(): _testAtoms() _testLattice() - # Now change values from the srfit DiffpyStructureParSet + # C3: The Parameters of the DiffpyStructureParSet are changed. + # Expected: The structure follows the changes, so the distance + # between the atoms changes. s.Cu0.x.set_value(0.456) s.Cu0.U22.set_value(0.441) s.Cu0.B13.set_value(0.550) @@ -95,8 +127,9 @@ def _testLattice(): s.lattice.alpha.set_value(91.3) _testAtoms() _testLattice() - # Make sure the distance changed - assert d != dsstru.lattice.dist(a1.xyz, a2.xyz) + actual_distance_changed = d != dsstru.lattice.dist(a1.xyz, a2.xyz) + expected_distance_changed = True + assert actual_distance_changed == expected_distance_changed return @@ -108,9 +141,11 @@ def test___repr__(): atom = Atom("C", [0, 0.2, 0.5]) structure = Structure([atom], lattice=lat) dsps = DiffpyStructureParSet("dsps", structure) - assert repr(structure) == repr(dsps) - assert repr(lat) == repr(dsps.lattice) - assert repr(atom) == repr(dsps.atoms[0]) + # C1: The structure, lattice and atom ParameterSets are printed. + # Expected: Each repr matches the repr of the adapted object. + actual_reprs = [repr(dsps), repr(dsps.lattice), repr(dsps.atoms[0])] + expected_reprs = [repr(structure), repr(lat), repr(atom)] + assert actual_reprs == expected_reprs return @@ -122,8 +157,14 @@ def test_pickling(): dsps = DiffpyStructureParSet("dsps", structure) data = pickle.dumps(dsps) dsps2 = pickle.loads(data) - assert 1 == len(dsps2.atoms) - assert 0.2 == dsps2.atoms[0].y.value + # C1: A DiffpyStructureParSet is pickled and unpickled. + # Expected: The copy keeps its single atom and the atom's position. + actual_atom_count = len(dsps2.atoms) + expected_atom_count = 1 + assert actual_atom_count == expected_atom_count + actual_y = dsps2.atoms[0].y.value + expected_y = 0.2 + assert actual_y == expected_y return diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index d8e295f..7e616e1 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -147,15 +147,21 @@ def testImplicitBondAngleRestraints(self): m = crystal.c60 m.wrap_restraints() - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) - - # make sure these evaluate to whatver we get from objcryst + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count + + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. res0, res1 = m._restraints - p0 = set([res0.penalty(), res1.penalty()]) + actual_penalties = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetBondAngleList() - p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - assert p0 == p1 + expected_penalties = set( + [angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()] + ) + assert actual_penalties == expected_penalties return @@ -166,49 +172,94 @@ def testObjCrystParSet(self): crystal = ObjCrystCrystalParSet("bucky", occryst) m = crystal.c60 - assert crystal.name == "bucky" + actual_name = crystal.name + expected_name = "bucky" + assert actual_name == expected_name def _testCrystal(): # Test the lattice - assert occryst.a == pytest.approx(crystal.a.value) - assert occryst.b == pytest.approx(crystal.b.get_value()) - assert occryst.c == pytest.approx(crystal.c.get_value()) - assert occryst.alpha == pytest.approx(crystal.alpha.get_value()) - assert occryst.beta == pytest.approx(crystal.beta.get_value()) - assert occryst.gamma == pytest.approx(crystal.gamma.get_value()) + actual_lattice = [ + crystal.a.value, + crystal.b.get_value(), + crystal.c.get_value(), + crystal.alpha.get_value(), + crystal.beta.get_value(), + crystal.gamma.get_value(), + ] + expected_lattice = [ + occryst.a, + occryst.b, + occryst.c, + occryst.alpha, + occryst.beta, + occryst.gamma, + ] + assert actual_lattice == pytest.approx(expected_lattice) return def _testMolecule(): - - # Test position / occupancy - assert ocmol.X == pytest.approx(m.x.get_value()) - assert ocmol.Y == pytest.approx(m.y.get_value()) - assert ocmol.Z == pytest.approx(m.z.get_value()) - assert ocmol.Occupancy == pytest.approx(m.occ.get_value()) - - # Test orientation - assert ocmol.Q0 == pytest.approx(m.q0.get_value()) - assert ocmol.Q1 == pytest.approx(m.q1.get_value()) - assert ocmol.Q2 == pytest.approx(m.q2.get_value()) - assert ocmol.Q3 == pytest.approx(m.q3.get_value()) + # Test position, occupancy and orientation + actual_molecule = [ + m.x.get_value(), + m.y.get_value(), + m.z.get_value(), + m.occ.get_value(), + m.q0.get_value(), + m.q1.get_value(), + m.q2.get_value(), + m.q3.get_value(), + ] + expected_molecule = [ + ocmol.X, + ocmol.Y, + ocmol.Z, + ocmol.Occupancy, + ocmol.Q0, + ocmol.Q1, + ocmol.Q2, + ocmol.Q3, + ] + assert actual_molecule == pytest.approx(expected_molecule) # Check the atoms thoroughly - for i in range(len(ocmol)): - oca = ocmol[i] - ocsp = oca.GetScatteringPower() - a = m.atoms[i] - assert ocsp.GetSymbol() == a.element - assert oca.X == pytest.approx(a.x.get_value()) - assert oca.Y == pytest.approx(a.y.get_value()) - assert oca.Z == pytest.approx(a.z.get_value()) - assert oca.Occupancy == pytest.approx(a.occ.get_value()) - assert ocsp.Biso == pytest.approx(a.Biso.get_value()) + actual_elements = [a.element for a in m.atoms] + expected_elements = [ + ocmol[i].GetScatteringPower().GetSymbol() + for i in range(len(ocmol)) + ] + assert actual_elements == expected_elements + actual_atoms = [ + [ + a.x.get_value(), + a.y.get_value(), + a.z.get_value(), + a.occ.get_value(), + a.Biso.get_value(), + ] + for a in m.atoms + ] + expected_atoms = [ + pytest.approx( + [ + ocmol[i].X, + ocmol[i].Y, + ocmol[i].Z, + ocmol[i].Occupancy, + ocmol[i].GetScatteringPower().Biso, + ] + ) + for i in range(len(ocmol)) + ] + assert actual_atoms == expected_atoms return + # C1: The ParameterSet has just been created from the crystal. + # Expected: The Parameters match the pyobjcryst values. _testCrystal() _testMolecule() - # Now change some values from ObjCryst + # C2: Values are changed through pyobjcryst. + # Expected: The Parameters follow the changes. ocmol[0].X *= 1.1 ocmol[0].Occupancy *= 1.1 ocmol[0].GetScatteringPower().Biso *= 1.1 @@ -218,7 +269,8 @@ def _testMolecule(): _testCrystal() _testMolecule() - # Now change values from the srfit StructureParSet + # C3: Values are changed through the ParameterSet. + # Expected: The pyobjcryst objects follow the changes. crystal.c60.C44.x.set_value(1.1) crystal.c60.C44.occ.set_value(1.1) crystal.c60.C44.Biso.set_value(1.1) @@ -243,15 +295,21 @@ def testImplicitBondLengthRestraints(self): m = crystal.c60 m.wrap_restraints() - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count - # make sure these evaluate to whatver we get from objcryst + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. res0, res1 = m._restraints - p0 = set([res0.penalty(), res1.penalty()]) + actual_penalties = set([res0.penalty(), res1.penalty()]) bonds = ocmol.GetBondList() - p1 = set([bonds[0].GetLogLikelihood(), bonds[1].GetLogLikelihood()]) - assert p0 == p1 + expected_penalties = set( + [bonds[0].GetLogLikelihood(), bonds[1].GetLogLikelihood()] + ) + assert actual_penalties == expected_penalties return @@ -273,15 +331,21 @@ def testImplicitDihedralAngleRestraints(self): m = crystal.c60 m.wrap_restraints() - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count - # make sure these evaluate to whatver we get from objcryst + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. res0, res1 = m._restraints - p0 = set([res0.penalty(), res1.penalty()]) + actual_penalties = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetDihedralAngleList() - p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - assert p0 == p1 + expected_penalties = set( + [angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()] + ) + assert actual_penalties == expected_penalties return @@ -303,15 +367,21 @@ def testExplicitBondLengthRestraints(self): res0 = m.restrain_bond_length(m.atoms[0], m.atoms[5], 3.3, 0.1, 0.1) res1 = m.restrain_bond_length(m.atoms[0], m.atoms[7], 3.3, 0.1, 0.1) - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count - # make sure these evaluate to whatver we get from objcryst - p0 = [res0.penalty(), res1.penalty()] + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. bonds = ocmol.GetBondList() - assert 2 == len(bonds) - p1 = [b.GetLogLikelihood() for b in bonds] - assert p0 == p1 + actual_bond_count = len(bonds) + expected_bond_count = 2 + assert actual_bond_count == expected_bond_count + actual_penalties = [res0.penalty(), res1.penalty()] + expected_penalties = [b.GetLogLikelihood() for b in bonds] + assert actual_penalties == expected_penalties return @@ -336,14 +406,20 @@ def testExplicitBondAngleRestraints(self): m.atoms[0], m.atoms[7], m.atoms[44], 3.3, 0.1, 0.1 ) - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count - # make sure these evaluate to whatver we get from objcryst - p0 = set([res0.penalty(), res1.penalty()]) + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. + actual_penalties = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetBondAngleList() - p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - assert p0 == p1 + expected_penalties = set( + [angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()] + ) + assert actual_penalties == expected_penalties return @@ -364,14 +440,20 @@ def testExplicitDihedralAngleRestraints(self): m.atoms[0], m.atoms[7], m.atoms[44], m.atoms[2], 1.1, 0.1, 0.1 ) - # make sure that we have some restraints in the molecule - assert 2 == len(m._restraints) + # C1: Two restraints are added to the molecule. + # Expected: The molecule holds both restraints. + actual_restraint_count = len(m._restraints) + expected_restraint_count = 2 + assert actual_restraint_count == expected_restraint_count - # make sure these evaluate to whatver we get from objcryst - p0 = set([res0.penalty(), res1.penalty()]) + # C2: The restraint penalties are evaluated. + # Expected: They equal the pyobjcryst log-likelihoods. + actual_penalties = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetDihedralAngleList() - p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - assert p0 == p1 + expected_penalties = set( + [angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()] + ) + assert actual_penalties == expected_penalties return @@ -404,17 +486,25 @@ def testExplicitBondLengthParameter(self): dd = xyz0 - xyz7 d0 = numpy.dot(dd, dd) ** 0.5 - assert d0 == pytest.approx(p1.get_value(), abs=1e-6) + # C1: A bond length Parameter is added. + # Expected: Its value is the current bond length. + actual_length = p1.get_value() + expected_length = d0 + assert actual_length == pytest.approx(expected_length, abs=1e-6) # Record the unit direction of change for later u = dd / d0 - # Change the value + # C2: The bond length Parameter is stretched by 5%. + # Expected: The Parameter and the measured bond length both take + # the new value, the first atom stays put, and the second and + # tag-along atoms move along the bond. scale = 1.05 p1.set_value(scale * d0) - # Verify that it has changed. - assert scale * d0 == pytest.approx(p1.get_value(), abs=1e-6) + actual_length = p1.get_value() + expected_length = scale * d0 + assert actual_length == pytest.approx(expected_length, abs=1e-6) xyz0a = numpy.array( [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] @@ -429,19 +519,23 @@ def testExplicitBondLengthParameter(self): dda = xyz0a - xyz7a d1 = numpy.dot(dda, dda) ** 0.5 - assert scale * d0 == pytest.approx(d1, abs=1e-6) - - # Verify that only the second and third atoms have moved. + actual_measured_length = d1 + expected_measured_length = scale * d0 + assert actual_measured_length == pytest.approx( + expected_measured_length, abs=1e-6 + ) - assert numpy.array_equal(xyz0, xyz0a) + actual_xyz0 = xyz0a.tolist() + expected_xyz0 = xyz0.tolist() + assert actual_xyz0 == expected_xyz0 - xyz7calc = xyz7 + (1 - scale) * d0 * u - for i in range(3): - assert xyz7a[i] == pytest.approx(xyz7calc[i], abs=1e-5) + actual_xyz7 = xyz7a + expected_xyz7 = xyz7 + (1 - scale) * d0 * u + assert actual_xyz7 == pytest.approx(expected_xyz7, abs=1e-5) - xyz20calc = xyz20 + (1 - scale) * d0 * u - for i in range(3): - assert xyz20a[i] == pytest.approx(xyz20calc[i], abs=1e-6) + actual_xyz20 = xyz20a + expected_xyz20 = xyz20 + (1 - scale) * d0 * u + assert actual_xyz20 == pytest.approx(expected_xyz20, abs=1e-6) return @@ -483,14 +577,21 @@ def testExplicitBondAngleParameter(self): # Have another atom tag along for the ride p1.add_atoms([a25]) - assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) + # C1: A bond angle Parameter is added. + # Expected: Its value is the current bond angle. + actual_angle = p1.get_value() + expected_angle = angle0 + assert actual_angle == pytest.approx(expected_angle, abs=1e-6) - # Change the value + # C2: The bond angle Parameter is stretched by 5%. + # Expected: The Parameter and the measured angle both take the new + # value, and only the third and tag-along atoms move. scale = 1.05 p1.set_value(scale * angle0) - # Verify that it has changed. - assert scale * angle0 == pytest.approx(p1.get_value(), abs=1e-6) + actual_angle = p1.get_value() + expected_angle = scale * angle0 + assert actual_angle == pytest.approx(expected_angle, abs=1e-6) xyz0a = numpy.array( [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] @@ -512,14 +613,20 @@ def testExplicitBondAngleParameter(self): angle1 = numpy.arccos(numpy.dot(v1a, v2a) / (d1a * d2a)) - assert scale * angle0 == pytest.approx(angle1, abs=1e-6) - - # Verify that only the last two atoms have moved. + actual_measured_angle = angle1 + expected_measured_angle = scale * angle0 + assert actual_measured_angle == pytest.approx( + expected_measured_angle, abs=1e-6 + ) - assert numpy.array_equal(xyz0, xyz0a) - assert numpy.array_equal(xyz7, xyz7a) - assert not numpy.array_equal(xyz20, xyz20a) - assert not numpy.array_equal(xyz25, xyz25a) + actual_moved = { + "C0": not numpy.array_equal(xyz0, xyz0a), + "C7": not numpy.array_equal(xyz7, xyz7a), + "C20": not numpy.array_equal(xyz20, xyz20a), + "C25": not numpy.array_equal(xyz25, xyz25a), + } + expected_moved = {"C0": False, "C7": False, "C20": True, "C25": True} + assert actual_moved == expected_moved return @@ -568,14 +675,21 @@ def testExplicitDihedralAngleParameter(self): # Have another atom tag along for the ride p1.add_atoms([a33]) - assert angle0 == pytest.approx(p1.get_value(), abs=1e-6) + # C1: A dihedral angle Parameter is added. + # Expected: Its value is the current dihedral angle. + actual_angle = p1.get_value() + expected_angle = angle0 + assert actual_angle == pytest.approx(expected_angle, abs=1e-6) - # Change the value + # C2: The dihedral angle Parameter is stretched by 5%. + # Expected: The Parameter and the measured angle both take the new + # value, and only the fourth and tag-along atoms move. scale = 1.05 p1.set_value(scale * angle0) - # Verify that it has changed. - assert scale * angle0 == pytest.approx(p1.get_value(), abs=1e-6) + actual_angle = p1.get_value() + expected_angle = scale * angle0 + assert actual_angle == pytest.approx(expected_angle, abs=1e-6) xyz0a = numpy.array( [a0.x.get_value(), a0.y.get_value(), a0.z.get_value()] @@ -602,15 +716,27 @@ def testExplicitDihedralAngleParameter(self): d123a = numpy.dot(v123a, v123a) ** 0.5 d234a = numpy.dot(v234a, v234a) ** 0.5 angle1 = -numpy.arccos(numpy.dot(v123a, v234a) / (d123a * d234a)) - assert scale * angle0 == pytest.approx(angle1, abs=1e-6) - - # Verify that only the last two atoms have moved. + actual_measured_angle = angle1 + expected_measured_angle = scale * angle0 + assert actual_measured_angle == pytest.approx( + expected_measured_angle, abs=1e-6 + ) - assert numpy.array_equal(xyz0, xyz0a) - assert numpy.array_equal(xyz7, xyz7a) - assert numpy.array_equal(xyz20, xyz20a) - assert not numpy.array_equal(xyz25, xyz25a) - assert not numpy.array_equal(xyz33, xyz33a) + actual_moved = { + "C0": not numpy.array_equal(xyz0, xyz0a), + "C7": not numpy.array_equal(xyz7, xyz7a), + "C20": not numpy.array_equal(xyz20, xyz20a), + "C25": not numpy.array_equal(xyz25, xyz25a), + "C33": not numpy.array_equal(xyz33, xyz33a), + } + expected_moved = { + "C0": False, + "C7": False, + "C20": False, + "C25": True, + "C33": True, + } + assert actual_moved == expected_moved return @@ -665,8 +791,9 @@ def xtestCreateSpaceGroup(self): if spacegroups.IsSpaceGroupIdentifier(short_name): space_group = spacegroups.GetSpaceGroup(shn) sgnew = self.getObjCrystParSetSpaceGroup(space_group) - # print("dbsg: " + repr(self.sgsEquivalent(sg, sgnew))) - assert self.sgsEquivalent(space_group, sgnew) + actual_equivalent = self.sgsEquivalent(space_group, sgnew) + expected_equivalent = True + assert actual_equivalent == expected_equivalent return diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index f4440f9..6479674 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -42,47 +42,58 @@ def test_ObjCryst_constrain_space_group(): structure.space_group_parameters.xyz_parameters structure.space_group_parameters.adp_parameters - # Check the orthorhombic lattice + # C1: The orthorhombic lattice of LaMnO3 in P b n m. + # Expected: The angles are fixed at pi / 2, the lengths are free, and + # no constraint equations are needed. lattice = structure.get_lattice() - assert lattice.alpha.const - assert lattice.beta.const - assert lattice.gamma.const - assert pi / 2 == lattice.alpha.get_value() - assert pi / 2 == lattice.beta.get_value() - assert pi / 2 == lattice.gamma.get_value() - - assert not lattice.a.const - assert not lattice.b.const - assert not lattice.c.const - assert 0 == len(lattice._constraints) - - # Now make sure the scatterers are constrained properly + lattice_names = ["a", "b", "c", "alpha", "beta", "gamma"] + actual_lattice_const = { + name: getattr(lattice, name).const for name in lattice_names + } + expected_lattice_const = { + "a": False, + "b": False, + "c": False, + "alpha": True, + "beta": True, + "gamma": True, + } + assert actual_lattice_const == expected_lattice_const + actual_angles = [ + lattice.alpha.get_value(), + lattice.beta.get_value(), + lattice.gamma.get_value(), + ] + expected_angles = [pi / 2, pi / 2, pi / 2] + assert actual_angles == expected_angles + actual_lattice_constraint_count = len(lattice._constraints) + expected_lattice_constraint_count = 0 + assert actual_lattice_constraint_count == expected_lattice_constraint_count + + # C2: The scatterers of LaMnO3 on their P b n m sites. + # Expected: Coordinates on special positions are fixed, the rest are + # free, and no constraint equations are needed. scatterers = structure.get_scatterers() - la = scatterers[0] - assert not la.x.const - assert not la.y.const - assert la.z.const - assert 0 == len(la._constraints) - - mn = scatterers[1] - assert mn.x.const - assert mn.y.const - assert mn.z.const - assert 0 == len(mn._constraints) - - o1 = scatterers[2] - assert not o1.x.const - assert not o1.y.const - assert o1.z.const - assert 0 == len(o1._constraints) - - o2 = scatterers[3] - assert not o2.x.const - assert not o2.y.const - assert not o2.z.const - assert 0 == len(o2._constraints) - - # Make sure we can't constrain these + la, mn, o1, o2 = scatterers + actual_xyz_const = { + "La1": [la.x.const, la.y.const, la.z.const], + "Mn1": [mn.x.const, mn.y.const, mn.z.const], + "O1": [o1.x.const, o1.y.const, o1.z.const], + "O2": [o2.x.const, o2.y.const, o2.z.const], + } + expected_xyz_const = { + "La1": [False, False, True], + "Mn1": [True, True, True], + "O1": [False, False, True], + "O2": [False, False, False], + } + assert actual_xyz_const == expected_xyz_const + actual_constraint_counts = [len(s._constraints) for s in scatterers] + expected_constraint_counts = [0, 0, 0, 0] + assert actual_constraint_counts == expected_constraint_counts + + # C3: Fixed coordinates are constrained or made into variables. + # Expected: A ValueError is raised. with pytest.raises(ValueError): mn.add_constraint(mn.x, "y") @@ -117,23 +128,45 @@ def test_DiffPy_constrain_as_space_group(datafile): constrainadps=True, ) - # Make sure that the new parameters were created - for parameter in space_group_parameters: - assert parameter is not None - assert parameter.get_value() is not None - - # Test the unconstrained atoms - for scatterer in parameter_set.get_scatterers()[1::2]: - assert not scatterer.x.const - assert not scatterer.y.const - assert not scatterer.z.const - assert not scatterer.U11.const - assert not scatterer.U22.const - assert not scatterer.U33.const - assert not scatterer.U12.const - assert not scatterer.U13.const - assert not scatterer.U23.const - assert 0 == len(scatterer._constraints) + # C1: The space group Parameters are created. + # Expected: Every Parameter exists and has a value. + actual_unset_parameters = [ + parameter + for parameter in space_group_parameters + if parameter is None or parameter.get_value() is None + ] + expected_unset_parameters = [] + assert actual_unset_parameters == expected_unset_parameters + + # C2: Scatterers that were not passed to constrain_as_space_group. + # Expected: Their positions and ADPs are free and unconstrained. + unconstrained = parameter_set.get_scatterers()[1::2] + actual_free_const = { + scatterer.name: [ + scatterer.x.const, + scatterer.y.const, + scatterer.z.const, + scatterer.U11.const, + scatterer.U22.const, + scatterer.U33.const, + scatterer.U12.const, + scatterer.U13.const, + scatterer.U23.const, + ] + for scatterer in unconstrained + } + expected_free_const = { + scatterer.name: [False] * 9 for scatterer in unconstrained + } + assert actual_free_const == expected_free_const + actual_free_constraint_counts = { + scatterer.name: len(scatterer._constraints) + for scatterer in unconstrained + } + expected_free_constraint_counts = { + scatterer.name: 0 for scatterer in unconstrained + } + assert actual_free_constraint_counts == expected_free_constraint_counts proxied = [p.par for p in space_group_parameters] @@ -153,25 +186,34 @@ def _alltests(parameter): or _proxytest(parameter) ) - for index, scatterer in enumerate(parameter_set.get_scatterers()[::2]): - # Under this scheme, atom 6 is free to vary - test = False - for parameter in [scatterer.x, scatterer.y, scatterer.z]: - test |= _alltests(parameter) - assert test - - test = False - for parameter in [ - scatterer.U11, - scatterer.U22, - scatterer.U33, - scatterer.U12, - scatterer.U13, - scatterer.U23, - ]: - test |= _alltests(parameter) - - assert test + # C3: Scatterers that were passed to constrain_as_space_group. + # Expected: At least one position and one ADP Parameter of each is + # fixed, constrained or proxied by a space group Parameter. + constrained = parameter_set.get_scatterers()[::2] + actual_restricted = { + scatterer.name: [ + any( + _alltests(parameter) + for parameter in [scatterer.x, scatterer.y, scatterer.z] + ), + any( + _alltests(parameter) + for parameter in [ + scatterer.U11, + scatterer.U22, + scatterer.U33, + scatterer.U12, + scatterer.U13, + scatterer.U23, + ] + ), + ] + for scatterer in constrained + } + expected_restricted = { + scatterer.name: [True, True] for scatterer in constrained + } + assert actual_restricted == expected_restricted return @@ -183,15 +225,23 @@ def test_constrain_as_space_group_args(datafile): from diffpy.cmistructure.sgconstraints import constrain_as_space_group from diffpy.structure.spacegroups import GetSpaceGroup + # C1: The space group is given as a symbol or as a SpaceGroup object. + # Expected: Both create the same space group Parameters. structure = makeLaMnO3_P1(datafile) parameter_set = DiffpyStructureParSet("LaMnO3", structure) - space_group_parameters = constrain_as_space_group(parameter_set, "P b n m") + symbol_parameters = constrain_as_space_group(parameter_set, "P b n m") space_group = GetSpaceGroup("P b n m") - parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) - sgpars2 = constrain_as_space_group(parset2, space_group) - list(space_group_parameters) - list(sgpars2) - assert space_group_parameters.names == sgpars2.names + object_parameter_set = DiffpyStructureParSet( + "LMO", makeLaMnO3_P1(datafile) + ) + object_parameters = constrain_as_space_group( + object_parameter_set, space_group + ) + list(symbol_parameters) + list(object_parameters) + actual_names = symbol_parameters.names + expected_names = object_parameters.names + assert actual_names == expected_names return From c6a87c06ea424ec43ace3107348d2731e03b83b4 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:47:34 -0400 Subject: [PATCH 10/13] chore: add diffpy.cmistructure headers to moved modules and tests Merge the diffpy.cmistructure header into the original diffpy.srfit header of each module and test moved from diffpy.srfit.structure. The Columbia University copyright and original author are kept, as the BSD license requires, and the header points to both LICENSE.rst and LICENSE_DANSE.rst. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/basestructureparset.py | 18 +++++++++++------- src/diffpy/cmistructure/bvsrestraint.py | 18 +++++++++++------- src/diffpy/cmistructure/cctbxparset.py | 18 +++++++++++------- src/diffpy/cmistructure/diffpyparset.py | 18 +++++++++++------- src/diffpy/cmistructure/objcrystparset.py | 18 +++++++++++------- src/diffpy/cmistructure/sgconstraints.py | 18 +++++++++++------- src/diffpy/cmistructure/srrealparset.py | 18 +++++++++++------- tests/test_diffpyparset.py | 18 +++++++++++------- tests/test_objcrystparset.py | 18 +++++++++++------- tests/test_sgconstraints.py | 18 +++++++++++------- 10 files changed, 110 insertions(+), 70 deletions(-) diff --git a/src/diffpy/cmistructure/basestructureparset.py b/src/diffpy/cmistructure/basestructureparset.py index 2aaf243..1dcae91 100644 --- a/src/diffpy/cmistructure/basestructureparset.py +++ b/src/diffpy/cmistructure/basestructureparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Base class for adapting structures to a ParameterSet interface. diff --git a/src/diffpy/cmistructure/bvsrestraint.py b/src/diffpy/cmistructure/bvsrestraint.py index 3912a61..9d3bc85 100644 --- a/src/diffpy/cmistructure/bvsrestraint.py +++ b/src/diffpy/cmistructure/bvsrestraint.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2010 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Bond-valence sum calculator from SrReal wrapped as a Restraint. diff --git a/src/diffpy/cmistructure/cctbxparset.py b/src/diffpy/cmistructure/cctbxparset.py index 9de0803..34161cd 100644 --- a/src/diffpy/cmistructure/cctbxparset.py +++ b/src/diffpy/cmistructure/cctbxparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Wrappers for interfacing cctbx crystal with SrFit. diff --git a/src/diffpy/cmistructure/diffpyparset.py b/src/diffpy/cmistructure/diffpyparset.py index 46918bf..36a318d 100644 --- a/src/diffpy/cmistructure/diffpyparset.py +++ b/src/diffpy/cmistructure/diffpyparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Adapters for interfacing a diffpy.structure.Structure with SrFit. diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index e348261..76309bb 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Wrappers for adapting pyobjcryst.crystal.Crystal to a srfit diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index 758062b..2a3d21e 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Code to set space group constraints for a crystal structure.""" diff --git a/src/diffpy/cmistructure/srrealparset.py b/src/diffpy/cmistructure/srrealparset.py index 7a3f897..d9ed71c 100644 --- a/src/diffpy/cmistructure/srrealparset.py +++ b/src/diffpy/cmistructure/srrealparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2009 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2009 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Structure wrapper class for structures compatible with SrReal.""" diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index 0d3b9f7..523dfc4 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2010 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Pavol Juhas +# File coded by: Pavol Juhas and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Tests for diffpy.cmistructure package.""" diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index 7e616e1..9dfad4d 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2010 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Pavol Juhas +# File coded by: Pavol Juhas and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Tests for diffpy.cmistructure package.""" diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 6479674..a918684 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -1,15 +1,19 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2010 The Trustees of Columbia University in the City of New York. +# (c) 2026 Contributors to diffpy.cmistructure. +# All rights reserved. # -# File coded by: Pavol Juhas +# File coded by: Pavol Juhas and members of the diffpy community. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# Originally developed in diffpy.srfit by the DANSE Diffraction group and +# Simon J. L. Billinge. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmistructure/graphs/contributors +# +# See LICENSE.rst and LICENSE_DANSE.rst for license information. # ############################################################################## """Tests space group constraints.""" From 07c16ef2e9942dac80df8f6c81417c9bc6d8c993 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 11:53:49 -0400 Subject: [PATCH 11/13] chore: add LICENSE_DANSE.rst from diffpy.srfit The modules moved from diffpy.srfit.structure are covered by the DANSE license from Columbia University, which must be distributed with them. Copy it verbatim from diffpy.srfit; MANIFEST.in already includes LICENSE*.rst in the sdist. Co-Authored-By: Claude Opus 5.5 --- LICENSE_DANSE.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 LICENSE_DANSE.rst diff --git a/LICENSE_DANSE.rst b/LICENSE_DANSE.rst new file mode 100644 index 0000000..c92ca2d --- /dev/null +++ b/LICENSE_DANSE.rst @@ -0,0 +1,34 @@ +This program is part of the DiffPy and DANSE open-source projects at Columbia +University and is available subject to the conditions and terms laid out below. + +Copyright (c) 2008-2011, The Trustees of Columbia University in +the City of New York. All rights reserved. + +For more information please visit the diffpy web-page at + http://www.diffpy.org +or email Prof. Simon Billinge at sb2896@columbia.edu. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the names of COLUMBIA UNIVERSITY, MICHIGAN STATE UNIVERSITY nor the + names of their contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 18c02f7de350c311219e461261411468d8c0e545 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 12:17:57 -0400 Subject: [PATCH 12/13] change python version to 3.11-3.13 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bc2621a..72f1f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "diffpy.cmi package for doing refinements with structure objects" keywords = ['diffraction', 'PDF', 'X-ray', 'neutron'] readme = "README.rst" -requires-python = ">=3.12, <3.15" +requires-python = ">=3.11, <3.14" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,9 +25,9 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', - 'Programming Language :: Python :: 3.14', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From d77495052cd84d2c9224ad2729367964345a3b97 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 7 Oct 2026 13:32:32 -0400 Subject: [PATCH 13/13] fix: use non-deprecated diffpy.structure APIs Replace calls that diffpy.structure has deprecated for removal in 4.0.0 with their snake_case equivalents: GetSpaceGroup -> get_space_group, positionFormulas -> position_formulas, UFormulas -> u_formulas, IsSpaceGroupIdentifier -> is_space_group_identifier and, in the tests, Lattice.setLatPar -> set_latt_parms. All of these exist in diffpy.structure 3.4.0 and later. Co-Authored-By: Claude Opus 5.5 --- src/diffpy/cmistructure/objcrystparset.py | 8 ++++---- src/diffpy/cmistructure/sgconstraints.py | 8 ++++---- tests/test_diffpyparset.py | 2 +- tests/test_objcrystparset.py | 4 ++-- tests/test_sgconstraints.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/diffpy/cmistructure/objcrystparset.py b/src/diffpy/cmistructure/objcrystparset.py index 76309bb..ef3ed1b 100644 --- a/src/diffpy/cmistructure/objcrystparset.py +++ b/src/diffpy/cmistructure/objcrystparset.py @@ -1919,7 +1919,7 @@ def _create_space_group(sgobjcryst): """ import copy - from diffpy.structure.spacegroups import GetSpaceGroup, SymOp + from diffpy.structure.spacegroups import SymOp, get_space_group name = sgobjcryst.GetName() extnstr = ":%s" % sgobjcryst.GetExtension() @@ -1927,9 +1927,9 @@ def _create_space_group(sgobjcryst): name = name[: -len(extnstr)] # Get whatever spacegroup we can get by name. This will set the proper - # crystal system. Creating a copy of the singleton from GetSpaceGroup, - # as this function messes with sg.symop_list. - space_group = copy.copy(GetSpaceGroup(name)) + # crystal system. Creating a copy of the singleton from + # get_space_group, as this function messes with symop_list. + space_group = copy.copy(get_space_group(name)) # Replace the symmetry operations to guarantee that we get it right. symops = sgobjcryst.GetSymmetryOperations() diff --git a/src/diffpy/cmistructure/sgconstraints.py b/src/diffpy/cmistructure/sgconstraints.py index 2a3d21e..38b01b8 100644 --- a/src/diffpy/cmistructure/sgconstraints.py +++ b/src/diffpy/cmistructure/sgconstraints.py @@ -103,11 +103,11 @@ def constrain_as_space_group( b and c are constrained to a, and alpha, beta and gamma are fixed to 90. """ - from diffpy.structure.spacegroups import GetSpaceGroup, SpaceGroup + from diffpy.structure.spacegroups import SpaceGroup, get_space_group space_group = spacegroup if not isinstance(spacegroup, SpaceGroup): - space_group = GetSpaceGroup(spacegroup) + space_group = get_space_group(spacegroup) sgp = _constrain_as_space_group( phase, space_group, @@ -494,7 +494,7 @@ def _constrain_xyzs(self, positions): self._xyz_parameters.add_parameter(newpar) # Constrain non-free xyz parameters - fpos = g.positionFormulas(xyznames) + fpos = g.position_formulas(xyznames) for index, tmp in enumerate(zip(scatterers, fpos)): scatterer, fp = tmp @@ -599,7 +599,7 @@ def _constrain_adps(self, positions): isosymbol, isoname, params=self._parameters ) - fadp = g.UFormulas(adpnames) + fadp = g.u_formulas(adpnames) # Constrain dependent anisotropics. We use the fact that an # anisotropic cannot be dependent on an isotropic. diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index 523dfc4..4c38ba6 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -116,7 +116,7 @@ def _testLattice(): a1.xyz[1] = 0.123 a1.U11 = 0.321 a1.B32 = 0.111 - dsstru.lattice.setLatPar(a=3.0, gamma=121) + dsstru.lattice.set_latt_parms(a=3.0, gamma=121) _testAtoms() _testLattice() diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index 9dfad4d..f236938 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -792,8 +792,8 @@ def xtestCreateSpaceGroup(self): for smbls in sgtbx.space_group_symbol_iterator(): shn = smbls.hermann_mauguin() short_name = shn.replace(" ", "") - if spacegroups.IsSpaceGroupIdentifier(short_name): - space_group = spacegroups.GetSpaceGroup(shn) + if spacegroups.is_space_group_identifier(short_name): + space_group = spacegroups.get_space_group(shn) sgnew = self.getObjCrystParSetSpaceGroup(space_group) actual_equivalent = self.sgsEquivalent(space_group, sgnew) expected_equivalent = True diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index a918684..15ce753 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -227,14 +227,14 @@ def test_constrain_as_space_group_args(datafile): function.""" from diffpy.cmistructure.diffpyparset import DiffpyStructureParSet from diffpy.cmistructure.sgconstraints import constrain_as_space_group - from diffpy.structure.spacegroups import GetSpaceGroup + from diffpy.structure.spacegroups import get_space_group # C1: The space group is given as a symbol or as a SpaceGroup object. # Expected: Both create the same space group Parameters. structure = makeLaMnO3_P1(datafile) parameter_set = DiffpyStructureParSet("LaMnO3", structure) symbol_parameters = constrain_as_space_group(parameter_set, "P b n m") - space_group = GetSpaceGroup("P b n m") + space_group = get_space_group("P b n m") object_parameter_set = DiffpyStructureParSet( "LMO", makeLaMnO3_P1(datafile) )