Source code for diffpy.cmistructure.objcrystparset

#!/usr/bin/env python
##############################################################################
#
# (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 and members of the diffpy community.
#
# 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
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.cmistructure.srrealparset import SrRealParSet
from diffpy.srfit.fitbase.parameter import (
    Parameter,
    ParameterAdapter,
    ParameterProxy,
)
from diffpy.srfit.fitbase.parameterset import ParameterSet


class ObjCrystScattererParSet(ParameterSet):
    """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.

    Attributes
    ----------
    scatterer : pyobjcryst.scatterer.Scatterer
        The adapted pyobjcryst object.
    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, scatterer, parent):
        """Initialize the scatterer ParameterSet.

        Parameters
        ----------
        name : str
            The name of the scatterer.
        scatterer : pyobjcryst.scatterer.Scatterer
            The pyobjcryst scatterer to adapt.
        parent : ParameterSet or None
            The ParameterSet this belongs to.
        """
        ParameterSet.__init__(self, name)
        self.scatterer = scatterer
        self.parent = parent

        # x, y, z, occ
        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.scatterer, attr="Occupancy")
        )
        return

    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 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):
    """Adapt a pyobjcryst.atom.Atom to the ParameterSet interface.

    This class derives from ObjCrystScattererParSet.

    Attributes
    ----------
    scatterer : 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 the atom ParameterSet.

        Parameters
        ----------
        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()

        # 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.scatterer.GetScatteringPower().GetSymbol()

    element = property(_getelem)


# End class ObjCrystAtomParSet


[docs] class ObjCrystMoleculeParSet(ObjCrystScattererParSet): """Adapt a pyobjcryst.molecule.Molecule to the ParameterSet interface. This class derives from ObjCrystScattererParSet. Other attributes are inherited from diffpy.srfit.fitbase.parameterset.ParameterSet. Attributes ---------- scatterer : pyobjcryst.molecule.Molecule The adapted molecule. structure : 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 the molecule ParameterSet. Parameters ---------- 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.structure = molecule # Add orientation quaternion 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 = [] 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
[docs] @classmethod def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- structure : object The structure object to check. Returns ------- bool The flag indicating if `structure` is a pyobjcryst Molecule. """ from pyobjcryst.molecule import Molecule return isinstance(structure, Molecule)
# Part of SrRealParSet interface
[docs] def use_symmetry(self, use=True): """Set whether this structure uses 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
[docs] def using_symmetry(self): """Return whether symmetry is being used. Returns ------- bool The flag indicating if symmetry is used. Always False, since 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.structure
[docs] 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.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.angle_units = "deg" return lattice
[docs] def get_scatterers(self): """Return the list of ParameterSets that represent the scatterers. Returns ------- list of ObjCrystMolAtomParSet The atom ParameterSets of the molecule. """ return self.atoms
[docs] def wrap_restraints(self): """Wrap the restraints implicit to the molecule. 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. for b in self.scatterer.GetBondList(): restraint = ObjCrystMoleculeRestraint(b) self.register_soft_bounds(restraint) for ba in self.scatterer.GetBondAngleList(): restraint = ObjCrystMoleculeRestraint(ba) self.register_soft_bounds(restraint) for da in self.scatterer.GetDihedralAngleList(): restraint = ObjCrystMoleculeRestraint(da) self.register_soft_bounds(restraint) return
[docs] def wrap_stretch_mode_parameters(self): """Wrap the stretch modes implicit to the Molecule as Parameters. 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. 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.scatterer.GetStretchModeBondLengthList(): name1 = mode.mpAtom0.GetName() name2 = mode.mpAtom1.GetName() name = "bl_" + "_".join((name1, name2)) atom1 = getattr(self, name1) atom2 = getattr(self, name2) parameter = ObjCrystBondLengthParameter( name, atom1, atom2, mode=mode ) atoms = [] for a in mode.GetAtoms(): name = a.GetName() atoms.append(getattr(self, name)) parameter.AddAtoms(atoms) self.add_parameter(parameter) for mode in self.scatterer.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) parameter = ObjCrystBondAngleParameter( name, atom1, atom2, atom3, mode=mode ) atoms = [] for a in mode.GetAtoms(): name = a.GetName() atoms.append(getattr(self, name)) parameter.AddAtoms(atoms) self.add_parameter(parameter) return
[docs] def restrain_bond_length( self, atom1, atom2, length, sigma, delta, scaled=False ): """Add a bond length restraint between two atoms. This creates an ObjCrystBondLengthRestraint and adds it to the ObjCrystMoleculeParSet. Parameters ---------- 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). Returns ------- ObjCrystBondLengthRestraint The restraint, for use with the ``remove_soft_bounds`` method. """ restraint = ObjCrystBondLengthRestraint( atom1, atom2, length, sigma, delta, scaled ) self.register_soft_bounds(restraint) return restraint
[docs] def restrain_bond_length_parameter( 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 `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- parameter : 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). Returns ------- ObjCrystBondLengthRestraint The restraint, for use with the ``remove_soft_bounds`` method. """ return self.restrain_bond_length( parameter.atom1, parameter.atom2, length, sigma, delta, scaled )
[docs] def restrain_bond_angle( self, atom1, atom2, atom3, angle, sigma, delta, scaled=False ): """Add a bond angle restraint between three atoms. This creates an ObjCrystBondAngleRestraint and adds it to the ObjCrystMoleculeParSet. Parameters ---------- 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 The restraint, for use with the ``remove_soft_bounds`` method. """ restraint = ObjCrystBondAngleRestraint( atom1, atom2, atom3, angle, sigma, delta, scaled ) self.register_soft_bounds(restraint) return restraint
[docs] def restrain_bond_angle_parameter( 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 `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- parameter : 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 The restraint, for use with the ``remove_soft_bounds`` method. """ return self.restrain_bond_angle( parameter.atom1, parameter.atom2, parameter.atom3, angle, sigma, delta, scaled, )
[docs] def restrain_dihedral_angle( self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False ): """Add a dihedral angle restraint between four atoms. This creates an ObjCrystDihedralAngleRestraint and adds it to the ObjCrystMoleculeParSet. Parameters ---------- 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 The restraint, for use with the ``remove_soft_bounds`` method. """ restraint = ObjCrystDihedralAngleRestraint( atom1, atom2, atom3, atom4, angle, sigma, delta, scaled ) self.register_soft_bounds(restraint) return restraint
[docs] def restrain_dihedral_angle_parameter( 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 `parameter` and adds it to the ObjCrystMoleculeParSet. Parameters ---------- parameter : 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 The restraint, for use with the ``remove_soft_bounds`` method. """ return self.restrain_dihedral_angle( parameter.atom1, parameter.atom2, parameter.atom3, parameter.atom4, angle, sigma, delta, scaled, )
[docs] def add_bond_length_parameter( self, name, atom1, atom2, value=None, const=False ): """Add a refinable bond length to the Molecule. This adds an ObjCrystBondLengthParameter to the ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- 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 The new bond length Parameter. """ parameter = ObjCrystBondLengthParameter( name, atom1, atom2, value, const ) self.add_parameter(parameter) return parameter
[docs] def add_bond_angle_parameter( self, name, atom1, atom2, atom3, value=None, const=False ): """Add a refinable bond angle to the Molecule. This adds an ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- 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 The new bond angle Parameter. """ parameter = ObjCrystBondAngleParameter( name, atom1, atom2, atom3, value, const ) self.add_parameter(parameter) return parameter
[docs] def add_dihedral_angle_parameter( self, name, atom1, atom2, atom3, atom4, value=None, const=False ): """Add a refinable dihedral angle to the Molecule. This adds an ObjCrystDihedralAngleParameter to the ObjCrystMoleculeParSet that can be adjusted during the fit. Parameters ---------- 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 The new dihedral angle Parameter. """ parameter = ObjCrystDihedralAngleParameter( name, atom1, atom2, atom3, atom4, value, const ) self.add_parameter(parameter) return parameter
# End class ObjCrystMoleculeParSet class ObjCrystMolAtomParSet(ObjCrystScattererParSet): """Adapt a pyobjcryst.molecule.MolAtom to the ParameterSet interface. 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 ---------- scatterer : 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, scatterer, parent): """Initialize the MolAtom ParameterSet. Parameters ---------- name : str The name of the atom. scatterer : pyobjcryst.molecule.MolAtom The MolAtom to adapt. parent : ObjCrystMoleculeParSet The molecule ParameterSet this belongs to. """ ObjCrystScattererParSet.__init__(self, name, scatterer, parent) sp = scatterer.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.scatterer.GetScatteringPower() if sp: return sp.GetSymbol() else: return "dummy" element = property(_getelem) 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.scatterer.IsDummy() # End class ObjCrystMolAtomParSet class ObjCrystMoleculeRestraint(object): """Base class for adapting pyobjcryst Molecule restraints to srfit. 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 ---------- restraint : object The pyobjcryst Molecule restraint. 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, restraint, scaled=False): """Wrap a pyobjcryst Molecule restraint as a Restraint. Parameters ---------- 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.restraint = restraint self.scaled = scaled return def __str__(self): """Return the restrained atoms with the restraint settings.""" restraint = self.restraint if hasattr(restraint, "GetLength0"): target = ( f"length = {restraint.GetLength0():f}, " f"sigma = {restraint.GetLengthSigma():f}, " f"delta = {restraint.GetLengthDelta():f}" ) else: target = ( f"angle = {restraint.GetAngle0():f}, " f"sigma = {restraint.GetAngleSigma():f}, " f"delta = {restraint.GetAngleDelta():f}" ) restraint_str = ( f"{restraint.GetName()}: {target}, scaled = {self.scaled}" ) return restraint_str def penalty(self, w=1.0): """Calculate the penalty of the restraint. Parameters ---------- 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.restraint.GetLogLikelihood() if self.scaled: penalty *= w return penalty # End class ObjCrystMoleculeRestraint class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): """Restrain the distance between two atoms. Attributes ---------- 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. restraint : 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). """ def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): """Initialize the bond length restraint. Parameters ---------- 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). """ self.atom1 = atom1 self.atom2 = atom2 m = self.atom1.scatterer.GetMolecule() restraint = m.AddBond( atom1.scatterer, atom2.scatterer, length, sigma, delta ) ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint length = property( lambda self: self.restraint.GetLength0(), lambda self, value: self.restraint.SetLength0(value), ) sigma = property( lambda self: self.restraint.GetLengthSigma(), lambda self, value: self.restraint.SetLengthSigma(value), ) delta = property( lambda self: self.restraint.GetLengthDelta(), lambda self, value: self.restraint.SetLengthDelta(value), ) # End class ObjCrystBondLengthRestraint class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the angle defined by three atoms. Attributes ---------- 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. restraint : 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). """ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): """Initialize the bond angle restraint. Parameters ---------- 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). """ self.atom1 = atom1 self.atom2 = atom2 self.atom3 = atom3 m = self.atom1.scatterer.GetMolecule() restraint = m.AddBondAngle( atom1.scatterer, atom2.scatterer, atom3.scatterer, angle, sigma, delta, ) ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint angle = property( lambda self: self.restraint.GetAngle0(), lambda self, value: self.restraint.SetAngle0(value), ) sigma = property( lambda self: self.restraint.GetAngleSigma(), lambda self, value: self.restraint.SetAngleSigma(value), ) delta = property( lambda self: self.restraint.GetAngleDelta(), lambda self, value: self.restraint.SetAngleDelta(value), ) # End class ObjCrystBondAngleRestraint class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the dihedral (torsion) angle defined by four atoms. Attributes ---------- 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. restraint : 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). """ def __init__( self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False ): """Initialize the dihedral angle restraint. Parameters ---------- 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). """ self.atom1 = atom1 self.atom2 = atom2 self.atom3 = atom3 self.atom4 = atom4 m = self.atom1.scatterer.GetMolecule() restraint = m.AddDihedralAngle( atom1.scatterer, atom2.scatterer, atom3.scatterer, atom4.scatterer, angle, sigma, delta, ) ObjCrystMoleculeRestraint.__init__(self, restraint, scaled) return # Give access to the parameters of the restraint angle = property( lambda self: self.restraint.GetAngle0(), lambda self, value: self.restraint.SetAngle0(value), ) sigma = property( lambda self: self.restraint.GetAngleSigma(), lambda self, value: self.restraint.SetAngleSigma(value), ) delta = property( lambda self: self.restraint.GetAngleDelta(), lambda self, value: self.restraint.SetAngleDelta(value), ) # End class ObjCrystDihedralAngleRestraint class StretchModeParameter(Parameter): """Partial Parameter class encapsulating pyobjcryst stretch modes. This class relies upon attributes that subclasses must set before calling ``StretchModeParameter.__init__``. Do not instantiate this class directly. Attributes ---------- 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. 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): """Initialize the stretch mode Parameter. Parameters ---------- 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, value): """Set the value of the Parameter by stretching the molecule. The stretch mode moves the mutated atoms by the change in value. Parameters ---------- value : float The new value of the Parameter. Returns ------- StretchModeParameter Return self so that mutators can be chained. """ curval = self.get_value() value = float(value) if value == curval: return self # The StretchMode expects the change in mutated value. delta = value - curval self.mode.Stretch(delta, self.keepcenter) # Let Parameter take care of the general details Parameter.set_value(self, value) return self 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. 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] # Record the added atoms in the Parameter self.mutated_atoms.update(atomlist) # Make sure we're observing these atoms for a in atomlist: a.x.add_observer(self._flush) a.y.add_observer(self._flush) a.z.add_observer(self._flush) # Record the added atoms in the StretchMode scatlist = [a.scatterer 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 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 for a in self.mutated_atoms: 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): """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 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 : ObjCrystMolAtomParSet The first atom in the bond. atom2 : ObjCrystMolAtomParSet The second (mutated) atom in the bond. mutated_atoms : 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): """Initialize the bond length Parameter. Parameters ---------- 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 if mode is 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.scatterer) self.mutated_atoms = set([atom2]) # Observe the atom positions for a in [atom1, atom2]: a.x.add_observer(self._flush) a.y.add_observer(self._flush) a.z.add_observer(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.scatterer, atom2.scatterer) 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 : bool, optional The flag indicating if the Parameter is constant (default True). 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. Returns ------- StretchModeParameter Return 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 def get_value(self): """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. Returns ------- float The bond length in Angstroms. """ if self._value is None: value = GetBondLength(self.atom1.scatterer, self.atom2.scatterer) Parameter.set_value(self, value) return self._value # End class ObjCrystBondLengthParameter class ObjCrystBondAngleParameter(StretchModeParameter): """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 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 : 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. mutated_atoms : 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 ): """Initialize the bond angle Parameter. Parameters ---------- 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 : 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 if mode is None: self.mode = StretchModeBondAngle( atom1.scatterer, atom2.scatterer, atom3.scatterer, None ) # We only add the last atom. This is the one that will move self.mode.AddAtom(atom3.scatterer) self.mutated_atoms = set([atom3]) # Observe the atom positions for a in [atom1, atom2, atom3]: a.x.add_observer(self._flush) a.y.add_observer(self._flush) a.z.add_observer(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.scatterer, atom2.scatterer, atom3.scatterer ) 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 : bool, optional The flag indicating if the Parameter is constant (default True). 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. 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]: a.x.set_constant(is_constant) a.y.set_constant(is_constant) a.z.set_constant(is_constant) return self def get_value(self): """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. Returns ------- float The bond angle in radians. """ if self._value is None: value = GetBondAngle( self.atom1.scatterer, self.atom2.scatterer, self.atom3.scatterer, ) Parameter.set_value(self, value) return self._value # End class ObjCrystBondAngleParameter class ObjCrystDihedralAngleParameter(StretchModeParameter): """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 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 : 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. mutated_atoms : 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__( self, name, atom1, atom2, atom3, atom4, value=None, const=False, mode=None, ): """Initialize the dihedral angle Parameter. Parameters ---------- 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 : 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 if mode is 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.scatterer) self.mutated_atoms = set([atom4]) # Observe the atom positions for a in [atom1, atom2, atom3, atom4]: a.x.add_observer(self._flush) a.y.add_observer(self._flush) a.z.add_observer(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.scatterer, atom2.scatterer, atom3.scatterer, atom4.scatterer, ) 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 : bool, optional The flag indicating if the Parameter is constant (default True). 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. 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]: a.x.set_constant(is_constant) a.y.set_constant(is_constant) a.z.set_constant(is_constant) return self def get_value(self): """Return the dihedral angle, recalculating it if needed. 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: value = GetDihedralAngle( self.atom1.scatterer, self.atom2.scatterer, self.atom3.scatterer, self.atom4.scatterer, ) Parameter.set_value(self, value) return self._value # End class ObjCrystDihedralAngleParameter
[docs] class ObjCrystCrystalParSet(SrRealParSet): """Adapt a pyobjcryst.crystal.Crystal to the ParameterSet interface. This class derives from SrRealParSet. See that class for base attributes. Attributes ---------- structure : 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. 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, crystal): """Initialize the crystal ParameterSet. Parameters ---------- name : str The name of this ParameterSet. crystal : 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.angle_units = "rad" self.structure = crystal self._sgpars = None 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.structure.GetNbScatterer()): s = self.structure.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": parameter_set = ObjCrystAtomParSet(name, s, self) elif cname == "Molecule": parameter_set = ObjCrystMoleculeParSet(name, s, self) else: raise TypeError("Unrecognized scatterer '%s'" % cname) 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 space_group = self._create_space_group(self.structure.GetSpaceGroup()) from diffpy.cmistructure.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, space_group, 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 SymOp, get_space_group 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 # 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() tranops = sgobjcryst.GetTranslationVectors() space_group.symop_list = [] for trans in tranops: for shift, rot in symops: tv = trans + shift tv -= numpy.floor(tv) space_group.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) space_group.symop_list.append(SymOp(-rot, tv)) return space_group
[docs] @classmethod def can_adapt(self, structure): """Return whether the structure can be adapted by this class. Parameters ---------- structure : object The structure object to check. Returns ------- bool The flag indicating if `structure` is a pyobjcryst Crystal. """ from pyobjcryst.crystal import Crystal return isinstance(structure, Crystal)
[docs] def get_lattice(self): """Return the ParameterSet containing the lattice Parameters. Returns ------- ObjCrystCrystalParSet This ParameterSet, which holds the lattice Parameters directly. """ return self
[docs] def get_scatterers(self): """Return the list of ParameterSets that represent the scatterers. Returns ------- list of ObjCrystAtomParSet or ObjCrystMoleculeParSet The scatterer ParameterSets of the crystal. """ return self.scatterers
# End class ObjCrystCrystalParSet