Source code for sisl.atom

""" Atomic information in different object containers.

Atomic information can be created and handled using the
`PeriodicTable` object or the `Atom` object, or lastly the combined
object `Atoms`.

* The `PeriodicTable` enables a *lookup* table for generic information
  about the atomic species in the periodic table of elements.
* The `Atom` enables creating atoms with associated information, such as

  * Mass
  * Species
  * Number of associated orbitals
  * Radii of each associated orbital
  * Custom tag (useful for denoting pseudo potentials)

* The `Atoms` object is a sorted, unique list of `Atom` such that one
  can contain a list of atoms. Instead of storing all `Atom` objects
  which may have *many* dublicates, the `Atoms` object has a unique
  list of `Atom` objects and an index list (`Atoms.specie`).
  This object enables a fast lookup of atoms without having
  to duplicate too much memory.

"""
from __future__ import print_function, division

# We need this for python3 support PY3
from six import with_metaclass

from numbers import Integral

import numpy as np

from ._help import array_fill_repeat, ensure_array, _str

__all__ = ['PeriodicTable', 'Atom', 'Atoms']


[docs]class PeriodicTable(object): """ Periodic table for creating an `Atom` via atomic numbers Enables *lookup* of atomic numbers/names/labels to get the atomic number. >>> 79 == PeriodicTable().Z('Au') >>> 79 == PeriodicTable().Z_int('Au') >>> 'Au' == PeriodicTable().Z_short(79) >>> 'Au' == PeriodicTable().Z_label(79) >>> 'Au' == PeriodicTable().Z_label('Gold') Several quantities available to the atomic species are available from <https://en.wikipedia.org/wiki/Atomic_radii_of_the_elements_(data_page)> The following values are accesible: * atomic mass (in atomic units) * empirical atomic radii (in Ang) * calculated atomic radii (in Ang) * van der Waals atomic radii (in Ang) For certain species the above quantities are not available and a negative number is returned. Examples -------- >>> 12.0107 == PeriodicTable().atomic_mass('C') True >>> 12.0107 == PeriodicTable().atomic_mass(6) True >>> 12.0107 == PeriodicTable().atomic_mass('Carbon') True >>> .67 == PeriodicTable().radii('Carbon') True >>> .67 == PeriodicTable().radii(6,'calc') True >>> .7 == PeriodicTable().radii(6,'empirical') True >>> 1.7 == PeriodicTable().radii(6,'vdw') True """ _Z_int = { 'Actinium': 89, 'Ac': 89, '89': 89, 89: 89, 'Aluminum': 13, 'Al': 13, '13': 13, 13: 13, 'Americium': 95, 'Am': 95, '95': 95, 95: 95, 'Antimony': 51, 'Sb': 51, '51': 51, 51: 51, 'Argon': 18, 'Ar': 18, '18': 18, 18: 18, 'Arsenic': 33, 'As': 33, '33': 33, 33: 33, 'Astatine': 85, 'At': 85, '85': 85, 85: 85, 'Barium': 56, 'Ba': 56, '56': 56, 56: 56, 'Berkelium': 97, 'Bk': 97, '97': 97, 97: 97, 'Beryllium': 4, 'Be': 4, '4': 4, 4: 4, 'Bismuth': 83, 'Bi': 83, '83': 83, 83: 83, 'Bohrium': 107, 'Bh': 107, '107': 107, 107: 107, 'Boron': 5, 'B': 5, '5': 5, 5: 5, 'Bromine': 35, 'Br': 35, '35': 35, 35: 35, 'Cadmium': 48, 'Cd': 48, '48': 48, 48: 48, 'Calcium': 20, 'Ca': 20, '20': 20, 20: 20, 'Californium': 98, 'Cf': 98, '98': 98, 98: 98, 'Carbon': 6, 'C': 6, '6': 6, 6: 6, 'Cerium': 58, 'Ce': 58, '58': 58, 58: 58, 'Cesium': 55, 'Cs': 55, '55': 55, 55: 55, 'Chlorine': 17, 'Cl': 17, '17': 17, 17: 17, 'Chromium': 24, 'Cr': 24, '24': 24, 24: 24, 'Cobalt': 27, 'Co': 27, '27': 27, 27: 27, 'Copper': 29, 'Cu': 29, '29': 29, 29: 29, 'Curium': 96, 'Cm': 96, '96': 96, 96: 96, 'Darmstadtium': 110, 'Ds': 110, '110': 110, 110: 110, 'Dubnium': 105, 'Db': 105, '105': 105, 105: 105, 'Dysprosium': 66, 'Dy': 66, '66': 66, 66: 66, 'Einsteinium': 99, 'Es': 99, '99': 99, 99: 99, 'Erbium': 68, 'Er': 68, '68': 68, 68: 68, 'Europium': 63, 'Eu': 63, '63': 63, 63: 63, 'Fermium': 100, 'Fm': 100, '100': 100, 100: 100, 'Fluorine': 9, 'F': 9, '9': 9, 9: 9, 'Francium': 87, 'Fr': 87, '87': 87, 87: 87, 'Gadolinium': 64, 'Gd': 64, '64': 64, 64: 64, 'Gallium': 31, 'Ga': 31, '31': 31, 31: 31, 'Germanium': 32, 'Ge': 32, '32': 32, 32: 32, 'Gold': 79, 'Au': 79, '79': 79, 79: 79, 'Hafnium': 72, 'Hf': 72, '72': 72, 72: 72, 'Hassium': 108, 'Hs': 108, '108': 108, 108: 108, 'Helium': 2, 'He': 2, '2': 2, 2: 2, 'Holmium': 67, 'Ho': 67, '67': 67, 67: 67, 'Hydrogen': 1, 'H': 1, '1': 1, 1: 1, 'Indium': 49, 'In': 49, '49': 49, 49: 49, 'Iodine': 53, 'I': 53, '53': 53, 53: 53, 'Iridium': 77, 'Ir': 77, '77': 77, 77: 77, 'Iron': 26, 'Fe': 26, '26': 26, 26: 26, 'Krypton': 36, 'Kr': 36, '36': 36, 36: 36, 'Lanthanum': 57, 'La': 57, '57': 57, 57: 57, 'Lawrencium': 103, 'Lr': 103, '103': 103, 103: 103, 'Lead': 82, 'Pb': 82, '82': 82, 82: 82, 'Lithium': 3, 'Li': 3, '3': 3, 3: 3, 'Lutetium': 71, 'Lu': 71, '71': 71, 71: 71, 'Magnesium': 12, 'Mg': 12, '12': 12, 12: 12, 'Manganese': 25, 'Mn': 25, '25': 25, 25: 25, 'Meitnerium': 109, 'Mt': 109, '109': 109, 109: 109, 'Mendelevium': 101, 'Md': 101, '101': 101, 101: 101, 'Mercury': 80, 'Hg': 80, '80': 80, 80: 80, 'Molybdenum': 42, 'Mo': 42, '42': 42, 42: 42, 'Neodymium': 60, 'Nd': 60, '60': 60, 60: 60, 'Neon': 10, 'Ne': 10, '10': 10, 10: 10, 'Neptunium': 93, 'Np': 93, '93': 93, 93: 93, 'Nickel': 28, 'Ni': 28, '28': 28, 28: 28, 'Niobium': 41, 'Nb': 41, '41': 41, 41: 41, 'Nitrogen': 7, 'N': 7, '7': 7, 7: 7, 'Nobelium': 102, 'No': 102, '102': 102, 102: 102, 'Osmium': 76, 'Os': 76, '76': 76, 76: 76, 'Oxygen': 8, 'O': 8, '8': 8, 8: 8, 'Palladium': 46, 'Pd': 46, '46': 46, 46: 46, 'Phosphorus': 15, 'P': 15, '15': 15, 15: 15, 'Platinum': 78, 'Pt': 78, '78': 78, 78: 78, 'Plutonium': 94, 'Pu': 94, '94': 94, 94: 94, 'Polonium': 84, 'Po': 84, '84': 84, 84: 84, 'Potassium': 19, 'K': 19, '19': 19, 19: 19, 'Praseodymium': 59, 'Pr': 59, '59': 59, 59: 59, 'Promethium': 61, 'Pm': 61, '61': 61, 61: 61, 'Protactinium': 91, 'Pa': 91, '91': 91, 91: 91, 'Radium': 88, 'Ra': 88, '88': 88, 88: 88, 'Radon': 86, 'Rn': 86, '86': 86, 86: 86, 'Rhenium': 75, 'Re': 75, '75': 75, 75: 75, 'Rhodium': 45, 'Rh': 45, '45': 45, 45: 45, 'Rubidium': 37, 'Rb': 37, '37': 37, 37: 37, 'Ruthenium': 44, 'Ru': 44, '44': 44, 44: 44, 'Rutherfordium': 104, 'Rf': 104, '104': 104, 104: 104, 'Samarium': 62, 'Sm': 62, '62': 62, 62: 62, 'Scandium': 21, 'Sc': 21, '21': 21, 21: 21, 'Seaborgium': 106, 'Sg': 106, '106': 106, 106: 106, 'Selenium': 34, 'Se': 34, '34': 34, 34: 34, 'Silicon': 14, 'Si': 14, '14': 14, 14: 14, 'Silver': 47, 'Ag': 47, '47': 47, 47: 47, 'Sodium': 11, 'Na': 11, '11': 11, 11: 11, 'Strontium': 38, 'Sr': 38, '38': 38, 38: 38, 'Sulfur': 16, 'S': 16, '16': 16, 16: 16, 'Tantalum': 73, 'Ta': 73, '73': 73, 73: 73, 'Technetium': 43, 'Tc': 43, '43': 43, 43: 43, 'Tellurium': 52, 'Te': 52, '52': 52, 52: 52, 'Terbium': 65, 'Tb': 65, '65': 65, 65: 65, 'Thallium': 81, 'Tl': 81, '81': 81, 81: 81, 'Thorium': 90, 'Th': 90, '90': 90, 90: 90, 'Thulium': 69, 'Tm': 69, '69': 69, 69: 69, 'Tin': 50, 'Sn': 50, '50': 50, 50: 50, 'Titanium': 22, 'Ti': 22, '22': 22, 22: 22, 'Tungsten': 74, 'W': 74, '74': 74, 74: 74, 'Ununbium': 112, 'Uub': 112, '112': 112, 112: 112, 'Ununhexium': 116, 'Uuh': 116, '116': 116, 116: 116, 'Ununoctium': 118, 'Uuo': 118, '118': 118, 118: 118, 'Ununpentium': 115, 'Uup': 115, '115': 115, 115: 115, 'Ununquadium': 114, 'Uuq': 114, '114': 114, 114: 114, 'Ununseptium': 117, 'Uus': 117, '117': 117, 117: 117, 'Ununtrium': 113, 'Uut': 113, '113': 113, 113: 113, 'Ununium': 111, 'Uuu': 111, '111': 111, 111: 111, 'Uranium': 92, 'U': 92, '92': 92, 92: 92, 'Vanadium': 23, 'V': 23, '23': 23, 23: 23, 'Xenon': 54, 'Xe': 54, '54': 54, 54: 54, 'Ytterbium': 70, 'Yb': 70, '70': 70, 70: 70, 'Yttrium': 39, 'Y': 39, '39': 39, 39: 39, 'Zinc': 30, 'Zn': 30, '30': 30, 30: 30, 'Zirconium': 40, 'Zr': 40, '40': 40, 40: 40, } _Z_short = { 'Actinium': 'Ac', 'Ac': 'Ac', '89': 'Ac', 89: 'Ac', 'Aluminum': 'Al', 'Al': 'Al', '13': 'Al', 13: 'Al', 'Americium': 'Am', 'Am': 'Am', '95': 'Am', 95: 'Am', 'Antimony': 'Sb', 'Sb': 'Sb', '51': 'Sb', 51: 'Sb', 'Argon': 'Ar', 'Ar': 'Ar', '18': 'Ar', 18: 'Ar', 'Arsenic': 'As', 'As': 'As', '33': 'As', 33: 'As', 'Astatine': 'At', 'At': 'At', '85': 'At', 85: 'At', 'Barium': 'Ba', 'Ba': 'Ba', '56': 'Ba', 56: 'Ba', 'Berkelium': 'Bk', 'Bk': 'Bk', '97': 'Bk', 97: 'Bk', 'Beryllium': 'Be', 'Be': 'Be', '4': 'Be', 4: 'Be', 'Bismuth': 'Bi', 'Bi': 'Bi', '83': 'Bi', 83: 'Bi', 'Bohrium': 'Bh', 'Bh': 'Bh', '107': 'Bh', 107: 'Bh', 'Boron': 'B', 'B': 'B', '5': 'B', 5: 'B', 'Bromine': 'Br', 'Br': 'Br', '35': 'Br', 35: 'Br', 'Cadmium': 'Cd', 'Cd': 'Cd', '48': 'Cd', 48: 'Cd', 'Calcium': 'Ca', 'Ca': 'Ca', '20': 'Ca', 20: 'Ca', 'Californium': 'Cf', 'Cf': 'Cf', '98': 'Cf', 98: 'Cf', 'Carbon': 'C', 'C': 'C', '6': 'C', 6: 'C', 'Cerium': 'Ce', 'Ce': 'Ce', '58': 'Ce', 58: 'Ce', 'Cesium': 'Cs', 'Cs': 'Cs', '55': 'Cs', 55: 'Cs', 'Chlorine': 'Cl', 'Cl': 'Cl', '17': 'Cl', 17: 'Cl', 'Chromium': 'Cr', 'Cr': 'Cr', '24': 'Cr', 24: 'Cr', 'Cobalt': 'Co', 'Co': 'Co', '27': 'Co', 27: 'Co', 'Copper': 'Cu', 'Cu': 'Cu', '29': 'Cu', 29: 'Cu', 'Curium': 'Cm', 'Cm': 'Cm', '96': 'Cm', 96: 'Cm', 'Darmstadtium': 'Ds', 'Ds': 'Ds', '110': 'Ds', 110: 'Ds', 'Dubnium': 'Db', 'Db': 'Db', '105': 'Db', 105: 'Db', 'Dysprosium': 'Dy', 'Dy': 'Dy', '66': 'Dy', 66: 'Dy', 'Einsteinium': 'Es', 'Es': 'Es', '99': 'Es', 99: 'Es', 'Erbium': 'Er', 'Er': 'Er', '68': 'Er', 68: 'Er', 'Europium': 'Eu', 'Eu': 'Eu', '63': 'Eu', 63: 'Eu', 'Fermium': 'Fm', 'Fm': 'Fm', '100': 'Fm', 100: 'Fm', 'Fluorine': 'F', 'F': 'F', '9': 'F', 9: 'F', 'Francium': 'Fr', 'Fr': 'Fr', '87': 'Fr', 87: 'Fr', 'Gadolinium': 'Gd', 'Gd': 'Gd', '64': 'Gd', 64: 'Gd', 'Gallium': 'Ga', 'Ga': 'Ga', '31': 'Ga', 31: 'Ga', 'Germanium': 'Ge', 'Ge': 'Ge', '32': 'Ge', 32: 'Ge', 'Gold': 'Au', 'Au': 'Au', '79': 'Au', 79: 'Au', 'Hafnium': 'Hf', 'Hf': 'Hf', '72': 'Hf', 72: 'Hf', 'Hassium': 'Hs', 'Hs': 'Hs', '108': 'Hs', 108: 'Hs', 'Helium': 'He', 'He': 'He', '2': 'He', 2: 'He', 'Holmium': 'Ho', 'Ho': 'Ho', '67': 'Ho', 67: 'Ho', 'Hydrogen': 'H', 'H': 'H', '1': 'H', 1: 'H', 'Indium': 'In', 'In': 'In', '49': 'In', 49: 'In', 'Iodine': 'I', 'I': 'I', '53': 'I', 53: 'I', 'Iridium': 'Ir', 'Ir': 'Ir', '77': 'Ir', 77: 'Ir', 'Iron': 'Fe', 'Fe': 'Fe', '26': 'Fe', 26: 'Fe', 'Krypton': 'Kr', 'Kr': 'Kr', '36': 'Kr', 36: 'Kr', 'Lanthanum': 'La', 'La': 'La', '57': 'La', 57: 'La', 'Lawrencium': 'Lr', 'Lr': 'Lr', '103': 'Lr', 103: 'Lr', 'Lead': 'Pb', 'Pb': 'Pb', '82': 'Pb', 82: 'Pb', 'Lithium': 'Li', 'Li': 'Li', '3': 'Li', 3: 'Li', 'Lutetium': 'Lu', 'Lu': 'Lu', '71': 'Lu', 71: 'Lu', 'Magnesium': 'Mg', 'Mg': 'Mg', '12': 'Mg', 12: 'Mg', 'Manganese': 'Mn', 'Mn': 'Mn', '25': 'Mn', 25: 'Mn', 'Meitnerium': 'Mt', 'Mt': 'Mt', '109': 'Mt', 109: 'Mt', 'Mendelevium': 'Md', 'Md': 'Md', '101': 'Md', 101: 'Md', 'Mercury': 'Hg', 'Hg': 'Hg', '80': 'Hg', 80: 'Hg', 'Molybdenum': 'Mo', 'Mo': 'Mo', '42': 'Mo', 42: 'Mo', 'Neodymium': 'Nd', 'Nd': 'Nd', '60': 'Nd', 60: 'Nd', 'Neon': 'Ne', 'Ne': 'Ne', '10': 'Ne', 10: 'Ne', 'Neptunium': 'Np', 'Np': 'Np', '93': 'Np', 93: 'Np', 'Nickel': 'Ni', 'Ni': 'Ni', '28': 'Ni', 28: 'Ni', 'Niobium': 'Nb', 'Nb': 'Nb', '41': 'Nb', 41: 'Nb', 'Nitrogen': 'N', 'N': 'N', '7': 'N', 7: 'N', 'Nobelium': 'No', 'No': 'No', '102': 'No', 102: 'No', 'Osmium': 'Os', 'Os': 'Os', '76': 'Os', 76: 'Os', 'Oxygen': 'O', 'O': 'O', '8': 'O', 8: 'O', 'Palladium': 'Pd', 'Pd': 'Pd', '46': 'Pd', 46: 'Pd', 'Phosphorus': 'P', 'P': 'P', '15': 'P', 15: 'P', 'Platinum': 'Pt', 'Pt': 'Pt', '78': 'Pt', 78: 'Pt', 'Plutonium': 'Pu', 'Pu': 'Pu', '94': 'Pu', 94: 'Pu', 'Polonium': 'Po', 'Po': 'Po', '84': 'Po', 84: 'Po', 'Potassium': 'K', 'K': 'K', '19': 'K', 19: 'K', 'Praseodymium': 'Pr', 'Pr': 'Pr', '59': 'Pr', 59: 'Pr', 'Promethium': 'Pm', 'Pm': 'Pm', '61': 'Pm', 61: 'Pm', 'Protactinium': 'Pa', 'Pa': 'Pa', '91': 'Pa', 91: 'Pa', 'Radium': 'Ra', 'Ra': 'Ra', '88': 'Ra', 88: 'Ra', 'Radon': 'Rn', 'Rn': 'Rn', '86': 'Rn', 86: 'Rn', 'Rhenium': 'Re', 'Re': 'Re', '75': 'Re', 75: 'Re', 'Rhodium': 'Rh', 'Rh': 'Rh', '45': 'Rh', 45: 'Rh', 'Rubidium': 'Rb', 'Rb': 'Rb', '37': 'Rb', 37: 'Rb', 'Ruthenium': 'Ru', 'Ru': 'Ru', '44': 'Ru', 44: 'Ru', 'Rutherfordium': 'Rf', 'Rf': 'Rf', '104': 'Rf', 104: 'Rf', 'Samarium': 'Sm', 'Sm': 'Sm', '62': 'Sm', 62: 'Sm', 'Scandium': 'Sc', 'Sc': 'Sc', '21': 'Sc', 21: 'Sc', 'Seaborgium': 'Sg', 'Sg': 'Sg', '106': 'Sg', 106: 'Sg', 'Selenium': 'Se', 'Se': 'Se', '34': 'Se', 34: 'Se', 'Silicon': 'Si', 'Si': 'Si', '14': 'Si', 14: 'Si', 'Silver': 'Ag', 'Ag': 'Ag', '47': 'Ag', 47: 'Ag', 'Sodium': 'Na', 'Na': 'Na', '11': 'Na', 11: 'Na', 'Strontium': 'Sr', 'Sr': 'Sr', '38': 'Sr', 38: 'Sr', 'Sulfur': 'S', 'S': 'S', '16': 'S', 16: 'S', 'Tantalum': 'Ta', 'Ta': 'Ta', '73': 'Ta', 73: 'Ta', 'Technetium': 'Tc', 'Tc': 'Tc', '43': 'Tc', 43: 'Tc', 'Tellurium': 'Te', 'Te': 'Te', '52': 'Te', 52: 'Te', 'Terbium': 'Tb', 'Tb': 'Tb', '65': 'Tb', 65: 'Tb', 'Thallium': 'Tl', 'Tl': 'Tl', '81': 'Tl', 81: 'Tl', 'Thorium': 'Th', 'Th': 'Th', '90': 'Th', 90: 'Th', 'Thulium': 'Tm', 'Tm': 'Tm', '69': 'Tm', 69: 'Tm', 'Tin': 'Sn', 'Sn': 'Sn', '50': 'Sn', 50: 'Sn', 'Titanium': 'Ti', 'Ti': 'Ti', '22': 'Ti', 22: 'Ti', 'Tungsten': 'W', 'W': 'W', '74': 'W', 74: 'W', 'Ununbium': 'Uub', 'Uub': 'Uub', '112': 'Uub', 112: 'Uub', 'Ununhexium': 'Uuh', 'Uuh': 'Uuh', '116': 'Uuh', 116: 'Uuh', 'Ununoctium': 'Uuo', 'Uuo': 'Uuo', '118': 'Uuo', 118: 'Uuo', 'Ununpentium': 'Uup', 'Uup': 'Uup', '115': 'Uup', 115: 'Uup', 'Ununquadium': 'Uuq', 'Uuq': 'Uuq', '114': 'Uuq', 114: 'Uuq', 'Ununseptium': 'Uus', 'Uus': 'Uus', '117': 'Uus', 117: 'Uus', 'Ununtrium': 'Uut', 'Uut': 'Uut', '113': 'Uut', 113: 'Uut', 'Ununium': 'Uuu', 'Uuu': 'Uuu', '111': 'Uuu', 111: 'Uuu', 'Uranium': 'U', 'U': 'U', '92': 'U', 92: 'U', 'Vanadium': 'V', 'V': 'V', '23': 'V', 23: 'V', 'Xenon': 'Xe', 'Xe': 'Xe', '54': 'Xe', 54: 'Xe', 'Ytterbium': 'Yb', 'Yb': 'Yb', '70': 'Yb', 70: 'Yb', 'Yttrium': 'Y', 'Y': 'Y', '39': 'Y', 39: 'Y', 'Zinc': 'Zn', 'Zn': 'Zn', '30': 'Zn', 30: 'Zn', 'Zirconium': 'Zr', 'Zr': 'Zr', '40': 'Zr', 40: 'Zr', } _atomic_mass = { 1: 1.00794, 2: 4.002602, 3: 6.941, 4: 9.012182, 5: 10.811, 6: 12.0107, 7: 14.0067, 8: 15.9994, 9: 18.9984032, 10: 20.1797, 11: 22.98976928, 12: 24.3050, 13: 26.9815386, 14: 28.0855, 15: 30.973762, 16: 32.065, 17: 35.453, 18: 39.948, 19: 39.0983, 20: 40.078, 21: 44.955912, 22: 47.867, 23: 50.9415, 24: 51.9961, 25: 54.938045, 26: 55.845, 27: 58.933195, 28: 58.6934, 29: 63.546, 30: 65.409, 31: 69.723, 32: 72.64, 33: 74.92160, 34: 78.96, 35: 79.904, 36: 83.798, 37: 85.4678, 38: 87.62, 39: 88.90585, 40: 91.224, 41: 92.906, 42: 95.94, 43: 98., 44: 101.07, 45: 102.905, 46: 106.42, 47: 107.8682, 48: 112.411, 49: 114.818, 50: 118.710, 51: 121.760, 52: 127.60, 53: 126.904, 54: 131.293, 55: 132.9054519, 56: 137.327, 57: 138.90547, 58: 140.116, 59: 140.90765, 60: 144.242, 61: 145., 62: 150.36, 63: 151.964, 64: 157.25, 65: 158.92535, 66: 162.500, 67: 164.930, 68: 167.259, 69: 168.93421, 70: 173.04, 71: 174.967, 72: 178.49, 73: 180.94788, 74: 183.84, 75: 186.207, 76: 190.23, 77: 192.217, 78: 195.084, 79: 196.966569, 80: 200.59, 81: 204.3833, 82: 207.2, 83: 208.98040, 84: 210., 85: 210., 86: 220., 87: 223., 88: 226., 89: 227., 91: 231.03588, 90: 232.03806, 93: 237., 92: 238.02891, 95: 243., 94: 244., 96: 247., 97: 247., 98: 251., 99: 252., 100: 257., 101: 258., 102: 259., 103: 262., 104: 261., 105: 262., 106: 266., 107: 264., 108: 277., 109: 268., 110: 271., 111: 272., 112: 285., 113: 284., 114: 289., 115: 288., 116: 292., 118: 293., } _radius_empirical = { 1: 25, 2: -1, 3: 145, 4: 105, 5: 85, 6: 70, 7: 65, 8: 60, 9: 50, 10: -1, 11: 180, 12: 150, 13: 125, 14: 110, 15: 100, 16: 100, 17: 100, 18: 71, 19: 220, 20: 180, 21: 160, 22: 140, 23: 135, 24: 140, 25: 140, 26: 140, 27: 135, 28: 135, 29: 135, 30: 135, 31: 130, 32: 125, 33: 115, 34: 115, 35: 115, 36: -1, 37: 235, 38: 200, 39: 180, 40: 155, 41: 145, 42: 145, 43: 135, 44: 130, 45: 135, 46: 140, 47: 160, 48: 155, 49: 155, 50: 145, 51: 145, 52: 140, 53: 140, 54: -1, 55: 260, 56: 215, 57: 195, 58: 185, 59: 185, 60: 185, 61: 185, 62: 185, 63: 185, 64: 180, 65: 175, 66: 175, 67: 175, 68: 175, 69: 175, 70: 175, 71: 175, 72: 155, 73: 145, 74: 135, 75: 135, 76: 130, 77: 135, 78: 135, 79: 135, 80: 150, 81: 190, 82: 180, 83: 160, 84: 190, 85: -1, 86: -1, 87: -1, 88: 215, 89: 195, 90: 180, 91: 180, 92: 175, 93: 175, 94: 175, 95: 175, 96: -1, 97: -1, 98: -1, 99: -1, 100: -1, 101: -1, 102: -1, 103: -1, 104: -1, 105: -1, 106: -1, 107: -1, 108: -1, 109: -1, 110: -1, 111: -1, 112: -1, 113: -1, 114: -1, 115: -1, 116: -1, 117: -1, 118: -1, } _radius_calc = { 1: 53, 2: 31, 3: 167, 4: 112, 5: 87, 6: 67, 7: 56, 8: 48, 9: 42, 10: 38, 11: 190, 12: 145, 13: 118, 14: 111, 15: 98, 16: 88, 17: 79, 18: 71, 19: 243, 20: 194, 21: 184, 22: 176, 23: 171, 24: 166, 25: 161, 26: 156, 27: 152, 28: 149, 29: 145, 30: 142, 31: 136, 32: 125, 33: 114, 34: 103, 35: 94, 36: 88, 37: 265, 38: 219, 39: 212, 40: 206, 41: 198, 42: 190, 43: 183, 44: 178, 45: 173, 46: 169, 47: 165, 48: 161, 49: 156, 50: 145, 51: 133, 52: 123, 53: 115, 54: 108, 55: 298, 56: 253, 57: -1, 58: -1, 59: 247, 60: 206, 61: 205, 62: 238, 63: 231, 64: 233, 65: 225, 66: 228, 67: -1, 68: 226, 69: 222, 70: 222, 71: 217, 72: 208, 73: 200, 74: 193, 75: 188, 76: 185, 77: 180, 78: 177, 79: 174, 80: 171, 81: 156, 82: 154, 83: 143, 84: 135, 85: -1, 86: 120, 87: -1, 88: -1, 89: -1, 90: -1, 91: -1, 92: -1, 93: -1, 94: -1, 95: -1, 96: -1, 97: -1, 98: -1, 99: -1, 100: -1, 101: -1, 102: -1, 103: -1, 104: -1, 105: -1, 106: -1, 107: -1, 108: -1, 109: -1, 110: -1, 111: -1, 112: -1, 113: -1, 114: -1, 115: -1, 116: -1, 117: -1, 118: -1, } _radius_vdw = { 1: 120, 2: 140, 3: 182, 4: 153, 5: 192, 6: 170, 7: 155, 8: 152, 9: 147, 10: 154, 11: 227, 12: 173, 13: 184, 14: 210, 15: 180, 16: 180, 17: 175, 18: 188, 19: 275, 20: 231, 21: 211, 22: -1, 23: -1, 24: -1, 25: -1, 26: -1, 27: -1, 28: 163, 29: 140, 30: 139, 31: 187, 32: 211, 33: 185, 34: 190, 35: 185, 36: 202, 37: 303, 38: 249, 39: -1, 40: -1, 41: -1, 42: -1, 43: -1, 44: -1, 45: -1, 46: 163, 47: 172, 48: 158, 49: 193, 50: 217, 51: 206, 52: 206, 53: 198, 54: 216, 55: 343, 56: 268, 57: -1, 58: -1, 59: -1, 60: -1, 61: -1, 62: -1, 63: -1, 64: -1, 65: -1, 66: -1, 67: -1, 68: -1, 69: -1, 70: -1, 71: -1, 72: -1, 73: -1, 74: -1, 75: -1, 76: -1, 77: -1, 78: 175, 79: 166, 80: 155, 81: 196, 82: 202, 83: 207, 84: 197, 85: 202, 86: 220, 87: 348, 88: 283, 89: -1, 90: -1, 91: -1, 92: 186, 93: -1, 94: -1, 95: -1, 96: -1, 97: -1, 98: -1, 99: -1, 100: -1, 101: -1, 102: -1, 103: -1, 104: -1, 105: -1, 106: -1, 107: -1, 108: -1, 109: -1, 110: -1, 111: -1, 112: -1, 113: -1, 114: -1, 115: -1, 116: -1, 117: -1, 118: -1, }
[docs] def Z(self, key): """ Return the atomic number based on general input Return the atomic number corresponding to the `key` lookup. Parameters ---------- key : array_like or str or int Uses value to lookup the atomic number in the `PeriodicTable` object. Returns ------- Z : ndarray or int The atomic number corresponding to `key`, if `key` is array_like, so will the returned value be. Examples -------- >>> 79 == PeriodicTable().Z_int('Au') True >>> 79 == PeriodicTable().Z('Au') True >>> 6 == PeriodicTable().Z('Carbon') True """ ak = np.asarray([key]).flatten() if len(ak) == 1: return self._Z_int[ak[0]] return np.array([self._Z_int[i] for i in ak], np.int32)
Z_int = Z
[docs] def Z_label(self, key): """ Return the atomic label of the corresponding atom Return the atomic short name corresponding to the `key` lookup. Parameters ---------- key : array_like or str or int Uses value to lookup the atomic short name in the `PeriodicTable` object. Returns ------- name : ndarra or str The atomic short name corresponding to `key`, if `key` is array_like, so will the returned value be. """ ak = np.asarray([key]).flatten() if len(ak) == 1: return self._Z_short[ak[0]] return [self._Z_short[i] for i in ak]
Z_short = Z_label
[docs] def atomic_mass(self, key): """ Return the atomic mass of the corresponding atom Return the atomic mass corresponding to the `key` lookup. Parameters ---------- key : array_like or str or int Uses value to lookup the atomic mass in the `PeriodicTable` object. Returns ------- name : ndarray or float The atomic mass in atomic units corresponding to `key`, if `key` is array_like, so will the returned value be. """ Z = self.Z_int(key) if isinstance(Z, Integral): return self._atomic_mass[Z] return np.array([self._atomic_mass[i] for i in Z], np.float64)
[docs] def radius(self, key, method='calc'): """ Return the atomic radii Return the atomic radii. Parameters ---------- key : array_like or str or int Uses value to lookup the atomic mass in the `PeriodicTable` object. method : {'calc', 'empirical', 'vdw'} There are 3 different radii stored: 1. ``calc``, the calculated atomic radii 2. ``empirical``, the empirically found values 3. ``vdw``, the van-der-Waals found values Returns ------- radius : ndarray or float The atomic radius in `Ang` """ Z = self.Z_int(key) if method == 'calc': if isinstance(Z, Integral): return self._radius_calc[Z] / 100 return np.array([self._radius_calc[i] for i in Z], np.float64) / 100 elif method == 'empirical': if isinstance(Z, Integral): return self._radius_empirical[Z] / 100 return np.array([self._radius_empirical[i] for i in Z], np.float64) / 100 elif method == 'vdw': if isinstance(Z, Integral): return self._radius_vdw[Z] / 100 return np.array([self._radius_vdw[i] for i in Z], np.float64) / 100 raise ValueError( 'Method option could not be deciphered [calc|empirical|vdw].')
radii = radius
# Create a local instance of the periodic table to # faster look up _ptbl = PeriodicTable() class AtomMeta(type): """ Meta class for key-lookup on the class. """ def __getitem__(cls, key): """ Create a new atom object """ if isinstance(key, Atom): # if the key already is an atomic object # return it return key if isinstance(key, dict): # The key is a dictionary, hence # we can return the atom directly return cls(**key) if isinstance(key, list): # The key is a list, # we need to create a list of atoms atm = [cls[k] for k in key] return np.array(atm, dtype=Atom) if isinstance(key, cls): # if the key already is an atomic object # return it return key # Index Z based return cls(key) # Note the with_metaclass which is required for python3 support. # The designation of metaclass in python3 is actually: # class ...(..., metaclass=MetaClass) # This below construct handles both python2 and python3 cases
[docs]class Atom(with_metaclass(AtomMeta, object)): """ Atomic information, mass, name number of orbitals and ranges Object to handle atomic mass, name, number of orbitals and orbital range. The `Atom` object handles the atomic species with information such as * atomic number * mass * number of orbitals * radius of each orbital The `Atom` object is `pickle`-able. Attributes ---------- Z : int atomic number R : ndarray radius of orbitals belonging to the `Atom` orbs : int number of orbitals belonging to the `Atom` mass : float mass of `Atom` Parameters ---------- Z : int or str key lookup for the atomic specie, `Atom[key]` R : array_like or float the range of the atomic orbitals orbs : int number of orbitals attached to this atom NOTE: Length of `R` precedes this quantity. mass : float, optional the atomic mass, if not specified uses the mass from `PeriodicTable` tag : str arbitrary designation for user handling similar atoms with different settings """ def __init__(self, Z, R=None, orbs=None, mass=None, tag=None): if isinstance(Z, Atom): Z = Z.Z if R is None: R = -1. if orbs is None: orbs = 1 self.Z = _ptbl.Z_int(Z) self.orbs = orbs try: self.orbs = max(len(R), orbs) except: pass self.R = array_fill_repeat( np.asarray([R], np.float64).flatten(), self.orbs) # Save the mass self.mass = mass if mass is None: self.mass = _ptbl.atomic_mass(self.Z) if tag is None: self.tag = self.symbol else: self.tag = tag
[docs] def copy(self): """ Return copy of this object """ return self.__class__(self.Z, self.R, self.orbs, self.mass, self.tag)
[docs] def radius(self, method='calc'): """ Return the atomic radii of the atom (in Ang) See `PeriodicTable.radius` for details on the argument. """ return _ptbl.radius(self.Z, method)
radii = radius @property def symbol(self): """ Return short atomic name (Au==79). """ return _ptbl.Z_short(self.Z) @property def dR(self): """ Return the maximum range of orbitals. """ return np.amax(self.R)
[docs] def scale(self, scale): """ Scale the atomic radii and return an equivalent atom. Parameters ---------- scale : float the scale factor for the atomic radii """ new = self.copy() new.R = np.where(new.R > 0, new.R * scale, new.R) return new
def __repr__(self): return '{0}, Z: {1:d}, orbs: {2:d}, mass(au): {3:.5f}, dR: {4:.5f}'.format(self.tag, self.Z, self.orbs, self.mass, self.dR) def __len__(self): """ Return number of orbitals in this atom """ return self.orbs # Check whether they are equal def __eq__(a, b): """ Return true if the saved quantities are the same """ if not isinstance(b, Atom): return False same = a.Z == b.Z same &= a.orbs == b.orbs same &= a.mass == b.mass same &= a.tag == b.tag # This prevents an allclose being called with # different number of orbitals if same: same = np.allclose(a.R, b.R) return same def __ne__(a, b): return not (a == b) # Create pickling routines def __getstate__(self): """ Return the state of this object """ return {'Z': self.Z, 'orbs': self.orbs, 'mass': self.mass, 'tag': self.tag, 'R': self.R} def __setstate__(self, d): """ Re-create the state of this object """ self.__init__( d['Z'], R=d['R'], orbs=d['orbs'], mass=d['mass'], tag=d['tag'])
[docs]class Atoms(object): """ A list-like object to contain a list of different atoms with minimum data duplication. This holds multiple `Atom` objects which are indexed via a species index. This is convenient when having geometries with millions of atoms because it will not duplicate the `Atom` object, only a list index. Attributes ---------- atom : `list(Atom)` a list of unique atoms in this object specie : ndarray(int16) a list of unique specie indices """ # Using the slots should make this routine slightly faster. __slots__ = ['_atom', '_specie'] def __init__(self, atom=None, na=None): # Default value of the atom object if atom is None: atom = Atom('H') # Correct the atoms input to Atom if isinstance(atom, (np.ndarray, list, tuple)): # Convert to a list of unique elements # We can not use set because that is unordered # And we want the same order, always... uatom = [] specie = [0] * len(atom) if isinstance(atom[0], Atom): for i, a in enumerate(atom): try: s = uatom.index(a) except: s = -1 if s < 0: s = len(uatom) uatom.append(a) specie[i] = s elif isinstance(atom[0], (_str, Integral)): for i, a in enumerate(atom): a = Atom(a) try: s = uatom.index(a) except: s = -1 if s < 0: s = len(uatom) uatom.append(a) specie[i] = s else: raise ValueError('atom keyword was wrong input') elif isinstance(atom, (_str, Integral)): uatom = [Atom(atom)] specie = [0] elif isinstance(atom, Atom): uatom = [atom] specie = [0] elif isinstance(atom, Atoms): # Ensure we make a copy to not operate # on the same data. catom = atom.copy() uatom = catom.atom[:] specie = catom.specie[:] else: raise ValueError('atom keyword was wrong input') # Default for number of atoms if na is None: na = len(specie) # Create atom and species objects self._atom = list(uatom) self._specie = array_fill_repeat(specie, na, cls=np.int16)
[docs] def copy(self): """ Return a copy of this atom """ atoms = Atoms() atoms._atom = [a.copy() for a in self._atom] atoms._specie = np.copy(self._specie) return atoms
@property def atom(self): """ Return the unique atoms list """ return self._atom @property def specie(self): """ Return the specie list """ return self._specie @property def orbitals(self): """ Return an array of orbitals of the contained objects """ uorbs = np.array([a.orbs for a in self.atom], np.int32) return uorbs[self.specie[:]] @property def dR(self): """ Return an array of masses of the contained objects """ udR = np.array([a.dR for a in self.atom], np.float64) return udR[self.specie[:]] @property def mass(self): """ Return an array of masses of the contained objects """ umass = np.array([a.mass for a in self.atom], np.float64) return umass[self.specie[:]]
[docs] def scale(self, scale): """ Scale the atomic radii and return an equivalent atom. Parameters ---------- scale : float the scale factor for the atomic radii """ atoms = Atoms() atoms._atom = [a.scale(scale) for a in self._atom] atoms._specie = np.copy(self._specie) return atoms
[docs] def index(self, atom): """ Return the species index of the atom object """ for i, a in enumerate(self._atom): if a == atom: return i return -1
[docs] def reorder(self): """ Reorders the atoms and species index so that they are ascending """ smin = np.zeros(len(self.atom), np.int32) for i in range(len(self.atom)): lst = np.where(self.specie == i)[0] if len(lst) == 0: # means it is not in use smin[i] = len(self.specie) else: smin[i] = lst[0] # Now swap indices into correct place isort = np.argsort(smin) if np.all(np.diff(isort) == 0): # No swaps required return self.copy() # We need to swap something atoms = self.copy() for os, ns in zip(range(len(isort)), isort): # Reorder the atom array as well. atoms._atom[ns] = self._atom[os].copy() atoms._specie[self.specie == os] = ns return atoms
[docs] def reduce(self): """ Returns a new `Atoms` object by removing non-used atoms """ atoms = self.copy() atom = atoms._atom specie = atoms._specie rem = [] for i in range(len(self.atom)): if len(np.where(specie == i)[0]) == 0: rem.append(i) # Remove the atoms for i in rem[::-1]: atom.pop(i) specie = np.where(specie > i, specie - 1, specie) atoms._atom = atom atoms._specie = specie return atoms
[docs] def sub(self, atom): """ Return a subset of the list """ atom = ensure_array(atom).flatten() atoms = Atoms() atoms._atom = self._atom[:] atoms._specie = self._specie[atom] return atoms
[docs] def cut(self, seps, axis): """ Return a subset of the list """ atom = ensure_array(atom).flatten() atoms = Atoms() atoms._atom = self._atom[:] atoms._specie = self._specie[atom] return atoms
[docs] def remove(self, atom): """ Remove a set of atoms """ atms = ensure_array(atom).flatten() idx = np.setdiff1d(np.arange(len(self)), atms, assume_unique=True) return self.sub(idx)
[docs] def tile(self, reps): """ Tile this atom object """ atoms = self.copy() atoms._specie = np.tile(atoms._specie, reps) return atoms
[docs] def repeat(self, reps): """ Repeat this atom object """ atoms = self.copy() atoms._specie = np.repeat(atoms._specie, reps) return atoms
[docs] def swap(self, a, b): """ Swaps atoms """ a = ensure_array(a) b = ensure_array(b) atoms = self.copy() spec = np.copy(atoms._specie) atoms._specie[a] = spec[b] atoms._specie[b] = spec[a] return atoms
[docs] def append(self, other): """ Append `other` to this list of atoms and return the appended version """ if not isinstance(other, Atoms): other = Atoms(other) else: other = other.copy() atoms = self.copy() spec = np.copy(other._specie) for i, atom in enumerate(other.atom): if atom not in atoms: s = len(atoms.atom) atoms._atom.append(atom) else: s = atoms.index(atom) spec = np.where(spec == i, s, spec) atoms._specie = np.concatenate((atoms._specie, spec)) return atoms
add = append
[docs] def prepend(self, other): if not isinstance(other, Atoms): other = Atoms(other) return other.append(self)
[docs] def reverse(self, atom=None): """ Returns a reversed geometry Also enables reversing a subset of the atoms. """ atoms = self.copy() if atom is None: atoms._specie = atoms._specie[::-1] else: atoms._specie[atom] = atoms._specie[atom[::-1]] return atoms
[docs] def insert(self, index, other): """ Insert other atoms into the list of atoms at index """ if isinstance(other, Atom): other = Atoms(other) else: other = other.copy() # Create a copy for insertion atoms = self.copy() spec = other._specie[:] for i, atom in enumerate(other.atom): if atom not in atoms: s = len(atoms.atom) atoms._atom.append(atom) else: s = atoms.index(atom) spec = np.where(spec == i, s, spec) atoms._specie = np.insert(atoms._specie, index, spec) return atoms
def __repr__(self): """ Return the `Atoms` representation """ s = '{{Atoms({0}):\n'.format(len(self._atom)) for a, idx in self: s += ' ({0}) == [{1}], \n'.format(len(idx), a) return s + '}\n' def __len__(self): """ Return number of atoms in the object """ return len(self._specie) def __iter__(self): """ Loop on all atoms with the same specie in order This iterator returns two values: 1. The `Atom` object of the specie 2. A list of indices with all atoms being that specie """ for s, atom in enumerate(self._atom): yield atom, np.where(self.specie == s)[0] def __contains__(self, key): """ Determine whether the `key` is in the unique atoms list """ return key in self.atom def __getitem__(self, key): """ Return an `Atom` object corresponding to the key(s) """ if isinstance(key, slice): if key.step is None: nkey = slice(key.start or 0, key.stop or len(self), key.step or 1) elif key.step < 0: nkey = slice(key.stop or len(self)-1, key.start or -1, key.step) return [self.atom[self._specie[s]] for s in range(nkey.start, nkey.stop, nkey.step)] elif isinstance(key, (list, tuple, np.ndarray)): return [self.atom[self._specie[s]] for s in key] else: return self.atom[self._specie[key]] def __setitem__(self, key, value): """ Overwrite an `Atom` object corresponding to the key(s) """ # First we figure out if this is a new atom if isinstance(key, (list, np.ndarray, tuple)): atoms = Atoms(value, na=len(key)) else: atoms = Atoms(value) # Append the new Atom objects for atom, idx in atoms: if atom not in self: self._atom.append(atom) # Now the unique atom list also contains the new atoms # We need to re-create the species list if isinstance(key, (list, np.ndarray, tuple)): for i, j in enumerate(key): self._specie[j] = self.index(atoms[i]) else: self._specie[key] = self.index(atoms[0]) def __eq__(a, b): """ Returns true if the contained atoms are the same """ for atom in a.atom: if atom not in b: return False return True # Create pickling routines def __getstate__(self): """ Return the state of this object """ return {'atom': self.atom, 'specie': self.specie} def __setstate__(self, d): """ Re-create the state of this object """ self.__init__() self._atom = d['atom'] self._specie = d['specie']