****************
Fragment objects
****************
.. _fragment:
.. py:class:: Fragment

Fragment objects are used to hold geometry data of molecules and 
structures with periodic boundary conditions.

Fragments can be defined by direct setting of the object attributes,
or by reading in from XYZ (``'.xyz'``), ChemShell punch (``'.pun'``) or chemical JSON (``'.cjson'``) files as follows::

   h2o = Fragment(coords='water.xyz')

The h2o fragment defined by this can then be used in calculations
using the argument ``frag=h2o`` as appropriate.


Fragment properties
===================

.. py:attribute:: totalcharge

   Total charge of the system

.. py:attribute:: connect_mode

   Rules for determining atomic connectivity. 

   Allowed values:

   * ``'covalent'``:

   * ``'ionic'``:

.. py:attribute:: cell

   Object of :py:class:`<class Cell>` holding unit cell information for periodic systems

.. py:attribute:: natoms
  
   Number of atoms in the fragment

.. py:attribute:: nbqs
 
   Number of point charges in the fragment

.. py:attribute:: nshells

   Number of point charges in the fragment

.. py:attribute:: names

   Array of atom labels

.. py:attribute:: coords

   Array of atom coordinates in a.u. (natoms, 3)

.. py:attribute:: znums

   Array of atomic numbers (natoms)

.. py:attribute:: charges

   Array of atom charges (natoms)

.. py:attribute:: masses

   Array of atom masses (natoms) 

.. py:attribute:: bqs

   Object holding point charge information

.. py:attribute:: shells

   Object holding shell information


Fragment enquiry
================

.. py:method:: Fragment.centroid()

   Return the centroid of the fragment

.. py:method:: Fragment.radius()

   Return the radius of the smallest sphere which, when centred at the centroid
   of the fragment, encloses all the atoms

.. py:method:: Fragment.angle(i, j, k)

   Return the angle formed by atoms i-j-k in degrees

.. py:method:: Fragment.dihedral(i, j, k, l)

   Return the torsion angle of atoms i-j-k-l in degrees

.. py:method:: Fragment.hascharges()
 
   Return True if any atom charges have been set


Fragment functions
==================

.. py:method:: Fragment.save(filename=None, format='cjson')

   Write out fragment data to disk using specified file format 
   (``'cjson'``, ``'punch'`` or ``'xyz'``).

   Examples:

   * `save_punch_ethane`_
   * `save_xyz_ethane`_
.. _`save_punch_ethane`: ../../../../tests/fragment/save_punch_ethane.py
.. _`save_xyz_ethane`: ../../../../tests/fragment/save_xyz_ethane.py

.. py:method:: Fragment.connect()

   Compute connectivity table

.. py:method:: Fragment.connIJ(i, j)

   Add connection between atoms i and j

.. py:method:: Fragment.delete(todelete)

   Delete an atom or range of atoms

.. py:method:: Fragment.delete(toinsert, pos=0)

   Insert an atom or range of atoms at position ``pos``

.. py:method:: Fragment.merge(frag2)

   Merge fragments

.. py:method:: Fragment.addCharges(dictionary_of_charges)

   Add charges to labelled species as appropriate.

.. py:method:: Fragment.addShells(...)

   Add shells to core species with charges as specified.


Atoms selecting methods
=======================

The ChemShell :py:class:`Fragment` objects have a range of methods for
selecting particular atoms.


.. py:method:: Fragment.select(radius=0.0, atoms='', indices=[], regions=[], invert=False, union=False, return_masks=False, **kwargs)

   :param radius: Select atoms that lie within the radius distance (in a.u.) from the centre. See: :py:meth:`Fragment.selectByRadius`
   :type radius: float, optional

   :param atoms: See: :py:meth:`Fragment.selectByAtoms`
   :type atoms: str or list, optional

   :param indices: Select atoms by using one or more Python sequences. See: :py:meth:`Fragment.selectByIndices`
   :type indices: list, optional

   :param regions: :py:meth:`Fragment.selectByRegion`
   :type regions: list, optional

   :param invert: Invert the selection
   :type invert: bool, optional

   :param union: Return the union of results by all provided criterion parameters. By default (``union=False``) the intersection of all criteria is returned
   :type union: bool, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :param \**kwargs: Other keyword options, especially the following ones for biomolecular fragments (typically created from PDB/PQR/PSF):

   :param chainIDs: One chain ID or a list of multiple chain IDs, defaults to ``[]``. Please note chainIDs are string-type while resIDs and segIDs are integers.
   :type chainIDs: str or list

   :param resIDs: One residue ID or a list of multiple residue IDs, defaults to ``[]``. Please note chainIDs are string-type while resIDs and segIDs are integers.
   :type resIDs: int or list

   :param segIDs: One segment ID or a list of multiple segment IDs, defaults to ``[]``. Please note chainIDs are string-type while resIDs and segIDs are integers.
   :type regIDs: str or list

   :param residues: One residue name or a list of multiple residue names, defaults to ``[]``
   :type residues: str or list

   :param segments: One segment name or a list of multiple segment names, defaults to ``[]``
   :type segments: str or list

   :param truncate_by: Truncating method for ``side_chain``, defaults to ``''`` (no truncating). ChemShell currently supports ``'alpha-beta'`` which selects the side chain atoms of amino acids if ``side_chain=True`` or the backbone atoms of amino acids if ``side_chain=False``
   :type truncate_by: str

   :param side_chain: Select only the side chain if ``truncate_by`` is not ``''``, for example ``'alpha-beta'``, defaults to ``True``. ``side_chain=False`` selects the backbone atoms if ``truncate_by='alpha-beta'``. Currently amino acids are supported
   :type side_chain: list

   :param ff: Name of the forcefield scheme, defaults to ``'charmm'``
   :type ff: str

   :return: Return a sorted, non-repeating NumPy array of selected atom indices according to the provided criterion (criteria). If no valid criterion (criteria) provided, an **empty** array is returned (if ``return_masks=False``)
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

   :example:

.. code-block:: python

   my_cluster.select(regions=[2,3], atoms=['Mg','O'], strict=False, radius=10.0)

returns the indices of atoms of all regions 2 **or** 3, with names starting with "O" **or** "Mg", **and** lying within 10.0 Bohr

.. code-block:: python

   my_protein.select(residues=['ALA','CYS'], segments='PRT2', truncate_by='alpha-beta')

returns the indices of alanine **or** cysteine's side chain atoms on segment "PRT2"

.. code-block:: python

   my_protein.select(residues=['ALA'], truncate_by='alpha-beta', indices=[999,1000,1001], union=True)

returns the indices of alanines' side chain atoms **and** atoms 999, 1000, and 1001 (please note in Python the indexing starts from **0**)

.. seealso:: :py:meth:`Fragment.selectByAtoms` :py:meth:`Fragment.selectByIndices` :py:meth:`Fragment.selectByRadius` :py:meth:`Fragment.selectByRegions`


.. py:method:: Fragment.selectByAtoms(*args, strict=True, return_masks=False)

   :param \*args: One or more arguments of the allowed types. If more than one arguments are provided, the **union** of each criterion's result is returned. Nested lists are **not** allowed. Provide :py:class:`int` values for atomic numbers (for example, ``1`` matches all hydrogen atoms), and :py:class:`str` or :py:class:`bytes` for atom names
   :type \*args: int or str or bytes

   :param strict: Perform strict matching for atom names. ``strict=False`` allows loose matching of atom names that start with the given keywords. For example, in this case ``'H'`` matches ``'H11'`` and ``'H12'``
   :type strict: bool, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: A sorted NumPy array of selected atom indices according to the provided matching criterion (criteria). If no valid parameter is provided (for example, no matching), an **empty** array is returned
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

   :example:

.. code-block:: python

   my_frag.selectByAtoms('OM', 'CLA', 7, 'FE')

returns the indices of all atoms named as ``'OM'``, ``'CLA'``, or ``'FE'``, as well as all nitrogen atoms (atomic number ``7``)


.. py:method:: Fragment.selectByBackbone()

   :return: Return a sorted, non-repeating NumPy array of the indices of backbone atoms of proteins (yet to be implemented for nucleotides). Currently only the CHARMM forcefield scheme is supported
   :rtype: NumPy array of integers

.. note:: This method is only valid for biomolecular fragments (typically created from PDB/PQR/PSF)


.. py:method:: Fragment.selectByCommonNames(names, ff='charmm', side_chain=True, truncate_by='alpha-beta', invert=False, return_masks=False)

   :param names: A single name or a list of multiple names. If more than one names are given, the **union** of the results are returned. Currently accepted names include: ``'amino acids'``, ``'ions'``, ``'non-solvent'``, ``'protein'``, ``'solvent'`` (incl. ions), and ``'water'``. But residue and segment names can also be used in this method
   :type names: str or list

   :param ff: Name of the forcefield scheme, defaults to ``'charmm'``
   :type ff: str

   :param side_chain: Select only the side chain if ``truncate_by`` is not ``''``, for example ``'alpha-beta'``, defaults to ``True``. ``side_chain=False`` selects the backbone atoms if ``truncate_by='alpha-beta'``. Currently amino acids are supported. See :py:meth:`Fragment.select`
   :type side_chain: list

   :param truncate_by: Truncating method for ``side_chain``, defaults to ``''`` (no truncating). ChemShell currently supports ``'alpha-beta'`` which selects the side chain atoms of amino acids if ``side_chain=True`` or the backbone atoms of amino acids if ``side_chain=False``. See :py:meth:`Fragment.select`
   :type truncate_by: str

   :param invert: Invert the selection
   :type invert: bool, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: A sorted NumPy array of selected atom indices according to the given name(s). If no valid parameter is provided (for example, no matching), an **empty** array is returned
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

.. code-block:: python

   my_protein.selectByCommonNames(['amino-acid', 'ions'])

returns the indices of atoms on all amino acids' side chains (because by default ``side_chain=True`` and ``truncate_by='alpha-beta'``) **and** indices of ionic atoms (see also :py:meth:`Fragment.selectIons`).

.. note:: This method is only valid for biomolecular fragments (typically created from PDB/PQR/PSF)


.. py:method:: Fragment.selectByIndices(*args, return_masks=False)

   :param \*args: One or more arguments of the allowed types. Nested lists of any depth can be comprehended and flattened. See the example below
   :type \*args: int or list or range

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: Return a sorted, non-repeating NumPy array of selected atom indices according to the provided indexing. If no valid parameter is provided (for example, out of the range of the fragment's atoms), an **empty** array is returned
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

   :example:

.. code-block:: python

   my_frag.selectByIndices(99, range(2), [[range(10,12),11,[[1000,1003]]]])

returns ``[0 1 10 11 99 1000 1003]``

.. note:: Please keep in mind in Python the indexing starts from **0**

.. note:: :py:class:`tuple` cannot be used as an argument of :py:meth:`Fragment.selectByIndices`


.. py:method:: Fragment.selectByIons(ff='charmm', invert=False, return_masks=False)

   
   :param ff: Name of the forcefield scheme, defaults to ``'charmm'``
   :type ff: str

   :param invert: Invert the selection
   :type invert: bool, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: Return a sorted, non-repeating NumPy array of the indices of all ionic atoms (any atom with a name among "LIT", "SOD", "POT", "MG", "CAL", "RUB", "CES", "ZN2", "CD2", "CLA", "BAR", "OH", and "SO4" for the CHARMM forcefield)
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

.. note:: This method is only valid for biomolecular fragments (typically created from PDB/PQR/PSF)


.. py:method:: Fragment.selectByRadius(radius, range=[], centre=None, unit='a.u.', boundary=None, return_masks=False)

   :param radius: Radius distance (``unit='a.u.'`` by default) from the centre
   :type radius: float

   :param range: Contrain the selecting within this range of atom indices. The default range (``range=[]``) are all atoms
   :type range: list or NumPy array, optional

   :param centre: By default (``centre=None``) the fragment's centroid is taken as the centre. It is possible to assign either an atom's index as the centre, for example, ``centre=100``, or a list of specific 3D coordinates (in a.u.), for example, ``centre=[1.0,2.0,3.0]``
   :type centre: int or list, optional

   :param boundary: Policy about selecting (``boudnary='inclusive'``) or unselecting (``boundary='exclusive'``) molecules across the selection boundary. For **biomolecular fragments** (created from PDB/PQR/PSF, for example), the default policy is ``'inclusive'``. For other types of fragments, it defaults to ``None`` that the entireness of the boundary molecules will not be respected
   :type boundary: None or str, optional

   :param unit: Unit of the radius distance
   :type unit: str, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: Return an array of indices of the atoms within the given radius from the centre (default centre is the fragment's centroid). The returned array
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)


.. py:method:: Fragment.selectByRegions(*args, return_masks=False)


   :param \*args: One or more :py:class:`int` indicating the region suffix in atom names. Namely, ``1`` selects all atoms with names ending with ``'1'``, for example, ``'Mg1'`` and ``'O1'``. The region suffixes are defined by ChemShell's QM/MM :ref:`finite cluster model <cutting_clusters>`
   :type \*args: int

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: Return a sorted, non-repeating NumPy array of selected atom indices according to the given region number(s). The **union** of results by each region number is returned. If no valid parameter is provided, an **empty** array is returned
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

   :example:

.. code-block:: python

   my_frag.selectByRegions(1,2,3)

returns indices of all region 1, 2, and 3 atoms

.. note:: This method is for ChemShell's QM/MM :ref:`finite cluster model <cutting_clusters>` only

.. py:method:: Fragment.selectByShell(around=[], convex=True, padding=5.0, unit='a.u.', boundary=None|'inclusive', return_masks=False)

   :param around: Reference atoms around which the shell is to be cut out
   :type around: list or NumPy array, optional

   :param convex: Cut out a shell around the convex hull of the chosen reference atoms. This is default and faster, though it results in a shell slightly different from ``convex=False`` which takes the whole chosen reference atoms
   :type convex: bool, optional

   :param padding: Padding distance from the reference atoms: only atoms within this distance will be selected
   :type padding: float, optional

   :param unit: Unit for the padding distance
   :type unit: str, optional

   :param boundary: Policy about selecting (``boudnary='inclusive'``) or unselecting (``boundary='exclusive'``) molecules across the selection boundary. For **biomolecular fragments** (created from PDB/PQR/PSF, for example), the default policy is ``'inclusive'``. For other types of fragments, it defaults to ``None`` that the entireness of the boundary molecules will not be respected. See ``boundary`` of :py:meth:`Fragment.selectByRadius`
   :type boundary: None or str, optional

   :param return_masks: Return an array of masks (``True`` and ``False``) instead of indices
   :type return_masks: bool, optional

   :return: Return a sorted, non-repeating NumPy array of selected atom indices according to the given conditions. If no valid parameter is provided (for example ``around`` is left undefined), the indices of the whole fragment are returned
   :rtype: NumPy array of integers (default) or booleans (``return_masks=True``)

.. note:: Here the "shell" means a shell shape around the selected reference atoms. It should **not** be mistaken for the "shell" model of polarisable forcefield

.. note:: We have rewritten the ``convex=True`` method since v21.0.0. It now automatically includes all the interior atoms and runs way faster than ``convex=False``. It is worth noticing that the both methods result in slightly different selections, with the surface by the former being smoother

Cell object
===========

The cell object within the Fragment contains information 
on the unit cell of a periodic system.

.. py:attribute:: Fragment.ndimensions

   Number of dimensions of periodicity in the system. Can be ``0``, ``1``, ``2`` or ``3``.

.. py:attribute:: Fragment.cell.consts

   Lattice constants as a list (a, b, c, alpha, beta, gamma)

.. py:attribute:: Fragment.cell.vectors

   Cartesian cell vectors (in a.u.)

.. py:attribute:: Fragment.cell.fractional

   Array of atom fractional coordinates (natoms)

BQs object
==========

The BQs object within the Fragment contains information
on any point charges in the system.

.. py:attribute:: Fragment.bqs.names

   Array of point charge labels (nbqs)

.. py:attribute:: Fragment.bqs.coords

   Array of point charge coordinates in a.u. (nbqs, 3)

.. py:attribute:: Fragment.bqs.charges

   Array of point charge charges (nbqs)


Shells object
=============

The Shells object within the Fragment contains information
on any shells in the system.

.. py:attribute:: Fragment.shells.coreatoms

   Array of indices of parent atoms (nshells)

.. py:attribute:: Fragment.shells.names

   Array of shell labels (nshells)

.. py:attribute:: Fragment.shells.coords

   Array of absolute shell coordinates in a.u. (nshells, 3)

.. py:attribute:: Fragment.shells.displace

   Array of shell coordinates in a.u. relative to parent atom (nshells, 3)

.. py:attribute:: Fragment.shells.charges

   Array of shell charges (nshells)


