Source code for sisl.atom

from __future__ import print_function, division

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

from numbers import Integral, Real

import numpy as np

from .messages import info
from . import _array as _a
from ._indices import list_index_le
from ._help import array_fill_repeat, _str
from .shape import Sphere
from .orbital import Orbital

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


[docs]class PeriodicTable(object): r""" Periodic table for creating an `Atom`, or retrieval of atomic information via atomic numbers Enables *lookup* of atomic numbers/names/labels to get the atomic number. 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 -------- >>> 79 == PeriodicTable().Z('Au') True >>> 79 == PeriodicTable().Z_int('Au') True >>> 'Au' == PeriodicTable().Z_short(79) True >>> 'Au' == PeriodicTable().Z_label(79) True >>> 'Au' == PeriodicTable().Z_label('Gold') True >>> 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): """ 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 ------- numpy.ndarray or int 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 """ key = np.asarray(key).ravel() get = self._Z_int.get if len(key) == 1: return get(key[0], key[0]) return _a.asarrayi([get(ia, ia) for ia in key])
Z_int = Z
[docs] def Z_label(self, key): """ 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 ------- numpy.ndarray or str atomic short name corresponding to `key`, if `key` is array_like, so will the returned value be. """ ak = np.asarray(key).ravel() get = self._Z_short.get if len(ak) == 1: return get(ak[0], 'fa') return [get(ia, 'fa') for ia in ak]
Z_short = Z_label
[docs] def atomic_mass(self, key): """ 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 ------- numpy.ndarray or float atomic mass in atomic units corresponding to `key`, if `key` is array_like, so will the returned value be. """ Z = self.Z_int(key) get = self._atomic_mass.get if isinstance(Z, (Integral, Real)): return get(Z, 0.) return _a.arrayd([get(z, 0.) for z in Z])
[docs] def radius(self, key, method='calc'): """ Atomic radii using different methods 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 ------- numpy.ndarray or float atomic radius in `Ang` """ Z = self.Z_int(key) if method == 'calc': if isinstance(Z, Integral): return self._radius_calc[Z] / 100 return _a.arrayd([self._radius_calc[i] for i in Z]) / 100 elif method == 'empirical': if isinstance(Z, Integral): return self._radius_empirical[Z] / 100 return _a.arrayd([self._radius_empirical[i] for i in Z]) / 100 elif method == 'vdw': if isinstance(Z, Integral): return self._radius_vdw[Z] / 100 return _a.arrayd([self._radius_vdw[i] for i in Z]) / 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 elif isinstance(key, dict): # The key is a dictionary, hence # we can return the atom directly return cls(**key) elif isinstance(key, list): # The key is a list, # we need to create a list of atoms return [cls[k] for k in 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]@add_metaclass(AtomMeta) class Atom(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 no : int number of orbitals belonging to the `Atom` R : numpy.ndarray the range of each orbital associated with this `Atom` (see `Orbital.R` for details) q0 : numpy.ndarray the charge of each orbital associated with this `Atom` (see `Orbital.q0` for details) mass : float mass of `Atom` Parameters ---------- Z : int or str key lookup for the atomic specie, `Atom[key]` orbital : list of Orbital or float, optional all orbitals associated with this atom. Default to one orbital. mass : float, optional the atomic mass, if not specified uses the mass from `PeriodicTable` tag : str, optional arbitrary designation for user handling similar atoms with different settings (defaults to the label of the atom) """ def __init__(self, Z, orbital=None, mass=None, tag=None, **kwargs): if isinstance(Z, Atom): Z = Z.Z self.Z = _ptbl.Z_int(Z) self.orbital = None if isinstance(orbital, (tuple, list, np.ndarray)): if isinstance(orbital[0], Orbital): # all is good self.orbital = orbital elif isinstance(orbital[0], Real): # radius has been given self.orbital = [Orbital(R) for R in orbital] elif isinstance(orbital, Orbital): self.orbital = [orbital] elif isinstance(orbital, Real): self.orbital = [Orbital(orbital)] if self.orbital is None: if 'R' in kwargs: # backwards compatibility (possibly remove this in the future) R = _a.asarrayd(kwargs['R']).ravel() self.orbital = [Orbital(r) for r in R] else: self.orbital = [Orbital(-1.)] if mass is None: self.mass = _ptbl.atomic_mass(self.Z) else: self.mass = mass if tag is None: self.tag = self.symbol else: self.tag = tag @property def no(self): """ Number of orbitals on this atom """ return len(self.orbital) @property def R(self): """ Orbital radius """ return _a.arrayd([o.R for o in self.orbital]) @property def q0(self): """ Orbital initial charges """ return _a.arrayd([o.q0 for o in self.orbital])
[docs] def index(self, orbital): """ Return the index of the orbital in the atom object """ for i, o in enumerate(self.orbital): if o == orbital: return i raise KeyError('Could not find `orbital` in the list of orbitals.')
[docs] def sub(self, orbitals): """ Return the same atom with only a subset of the orbitals present Parameters ---------- orbitals : array_like indices of the orbitals to retain Returns ------- Atom with only the subset of orbitals Raises ------ ValueError : if the number of orbitals removed is too large or some indices are outside the allowed range """ orbitals = _a.arrayi(orbitals).ravel() if len(orbitals) > self.no: raise ValueError(self.__class__.__name__ + '.sub tries to remove more than the number of orbitals on an atom.') if np.any(orbitals >= self.no): raise ValueError(self.__class__.__name__ + '.sub tries to remove a non-existing orbital io > no.') orbs = [self.orbital[o].copy() for o in orbitals] return self.copy(orbital=orbs)
[docs] def remove(self, orbitals): """ Return the same atom without a specific set of orbitals Parameters ---------- orbitals : array_like indices of the orbitals to remove Returns ------- Atom without the specified orbitals See Also -------- sub : retain a selected set of orbitals """ orbs = np.delete(_a.arangei(self.no), orbitals) return self.sub(orbs)
[docs] def copy(self, Z=None, orbital=None, mass=None, tag=None): """ Return copy of this object """ return self.__class__(self.Z if Z is None else Z, self.orbital if orbital is None else orbital, self.mass if mass is None else mass, self.tag if tag is None else 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)
@property def symbol(self): """ Return short atomic name (Au==79). """ return _ptbl.Z_short(self.Z) def __getitem__(self, key): """ The orbital corresponding to index `key` """ if isinstance(key, slice): ol = key.indices(len(self)) return [self.orbital[o] for o in range(ol[0], ol[1], ol[2])] elif isinstance(key, Integral): return self.orbital[key] return [self.orbital[o] for o in np.asarray(key).ravel()]
[docs] def maxR(self): """ Return the maximum range of orbitals. """ mR = -1e10 for o in self.orbital: mR = max(mR, o.R) return mR
[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.orbital = [o.scale(scale) for o in self.orbital] return new
def __iter__(self): """ Loop on all orbitals in this atom """ for o in self.orbital: yield o
[docs] def iter(self, group=False): """ Loop on all orbitals in this atom Parameters ---------- group : bool, optional if two orbitals share the same radius one may be able to group two orbitals together Returns ------- Orbital current orbital, if `group` is ``True`` this is a list of orbitals, otherwise a single orbital is returned """ if group: i = 0 no = self.no - 1 while i <= no: # Figure out how many share the same radial part j = i + 1 while j <= no: if np.allclose(self.orbital[i].R, self.orbital[j].R): j += 1 else: break yield self.orbital[i:j] i = j return for o in self.orbital: yield o
def __str__(self): # Create orbitals output orbs = ',\n '.join([str(o) for o in self.orbital]) return self.__class__.__name__ + '{{{0}, Z: {1:d}, mass(au): {2:.5f}, maxR: {3:.5f},\n {4}\n}}'.format(self.tag, self.Z, self.mass, self.maxR(), orbs) def __len__(self): """ Return number of orbitals in this atom """ return self.no
[docs] def toSphere(self, center=None): """ Return a sphere with the maximum orbital radius equal Returns ------- ~sisl.shape.Sphere a sphere with radius equal to the maximum radius of the orbitals """ return Sphere(self.maxR(), center)
[docs] def equal(self, other, R=True, psi=False): """ True if `other` is the same as this atomic specie Parameters ---------- other : Atom the other object to check againts R : bool, optional if True the equality check also checks the orbital radii, else they are not compared psi : bool, optional if True, also check the wave-function component of the orbitals, see `Orbital.psi` """ if not isinstance(other, Atom): return False same = self.Z == other.Z same &= self.no == other.no if same and R: same &= all([self.orbital[i].equal(other.orbital[i], psi=psi) for i in range(self.no)]) same &= np.isclose(self.mass, other.mass) same &= self.tag == other.tag return same
# Check whether they are equal def __eq__(self, b): """ Return true if the saved quantities are the same """ return self.equal(b) def __ne__(self, b): return not (self == b) # Create pickling routines def __getstate__(self): """ Return the state of this object """ return {'Z': self.Z, 'orbital': self.orbital, 'mass': self.mass, 'tag': self.tag} def __setstate__(self, d): """ Re-create the state of this object """ self.__init__(d['Z'], d['orbital'], d['mass'], 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. Parameters ---------- atom : list of Atom atoms to be contained in this list of atoms na : int or None total number of atoms, if ``len(atom)`` is smaller than `na` it will be repeated to match `na`. Attributes ---------- atom : list of Atom a list of unique atoms in this object specie : (na, ) a list of unique specie indices no : int total number of orbitals q0 : (no, ) initial charge on each orbital mass : (na, ) mass for each atom firsto : (no + 1,) a list of orbital indices for each atom, this corresponds to the first orbital on each of the atoms. The last element is the total number of orbitals and is equivalent to `no`. lasto : (no, ) a list of orbital indices for each atom, this corresponds to the last orbital on each of the atoms. """ # Using the slots should make this class slightly faster. __slots__ = ['_atom', '_specie', '_firsto'] 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) self._update_orbitals() def _update_orbitals(self): """ Internal routine for updating the `firsto` attribute """ # Get number of orbitals per specie uorbs = _a.arrayi([a.no for a in self.atom]) self._firsto = np.insert(_a.cumsumi(uorbs[self.specie]), 0, 0)
[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) atoms._update_orbitals() return atoms
@property def atom(self): """ List of unique atoms in this group of atoms """ return self._atom @property def nspecie(self): """ Number of different species """ return len(self._atom) @property def specie(self): """ Atomic specie list """ return self._specie @property def no(self): """ Return the total number of orbitals in this list of atoms """ uorbs = _a.arrayi([a.no for a in self.atom]) return uorbs[self.specie].sum() @property def orbitals(self): """ Return an array of orbitals of the contained objects """ return np.diff(self.firsto) @property def firsto(self): """ The first orbital of the corresponding atom in the consecutive list of orbitals """ return self._firsto @property def lasto(self): """ The lasto orbital of the corresponding atom in the consecutive list of orbitals """ return self._firsto[1:] - 1 @property def q0(self): """ Initial charge per atom """ q0 = _a.arrayd([a.q0.sum() for a in self.atom]) return q0[self.specie]
[docs] def orbital(self, io): """ Return an array of orbital of the contained objects """ io = _a.asarrayi(io).ravel() % self.no a = list_index_le(io, self.lasto) io = io - self.firsto[a] a = self.specie[a] # Now extract the list of orbitals return [self.atom[ia].orbital[o] for ia, o in zip(a, io)]
[docs] def maxR(self, all=False): """ The maximum radius of the atoms Parameters ---------- all : bool determine the returned maximum radii. If `True` is passed an array of all atoms maximum radii is returned (array). Else, if `False` the maximum of all atoms maximum radii is returned (scalar). """ if all: maxR = _a.arrayd([a.maxR() for a in self.atom]) return maxR[self.specie[:]] return np.amax([a.maxR() for a in self.atom])
@property def mass(self): """ Return an array of masses of the contained objects """ umass = _a.arrayd([a.mass for a in self.atom]) return umass[self.specie[:]] @property def Z(self): """ Return an array of atomic numbers (integers) """ uZ = _a.arrayi([a.Z for a in self.atom]) return uZ[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 raise KeyError('Could not find `atom` in the list of atoms.')
[docs] def reorder(self, in_place=False): """ Reorders the atoms and species index so that they are ascending (starting with a specie that exists) Parameters ---------- in_place : bool, optional whether the re-order is done *in-place* """ # Contains the minimum atomic index for a given specie smin = _a.emptyi(len(self.atom)) smin.fill(len(self)) for a in range(len(self.atom)): lst = (self.specie == a).nonzero()[0] if len(lst) > 0: smin[a] = lst.min() # Now swap indices into correct place # This will give the indices of the species # in the ascending order isort = np.argsort(smin) if np.all(np.diff(isort) == 0): # No swaps required return self.copy() # We need to swap something if in_place: atoms = self else: atoms = self.copy() atoms._atom[:] = [atoms._atom[i] for i in isort] atoms._specie[:] = isort[atoms._specie] atoms._update_orbitals() return atoms
[docs] def reduce(self, in_place=False): """ Returns a new `Atoms` object by removing non-used atoms """ if in_place: atoms = self else: atoms = self.copy() atom = atoms._atom specie = atoms._specie rem = [] for i in range(len(self.atom)): if np.all(specie != i): 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 atoms._update_orbitals() return atoms
[docs] def sub(self, atom): """ Return a subset of the list """ atom = _a.asarrayi(atom).ravel() atoms = Atoms() atoms._atom = self._atom[:] atoms._specie = self._specie[atom] atoms._update_orbitals() return atoms
[docs] def remove(self, atom): """ Remove a set of atoms """ atom = _a.asarrayi(atom).ravel() idx = np.setdiff1d(np.arange(len(self)), atom, 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) atoms._update_orbitals() return atoms
[docs] def repeat(self, reps): """ Repeat this atom object """ atoms = self.copy() atoms._specie = np.repeat(atoms._specie, reps) atoms._update_orbitals() return atoms
[docs] def swap(self, a, b): """ Swaps all atoms """ a = _a.asarrayi(a) b = _a.asarrayi(b) atoms = self.copy() spec = np.copy(atoms._specie) atoms._specie[a] = spec[b] atoms._specie[b] = spec[a] atoms._update_orbitals() return atoms
[docs] def swap_atom(self, a, b): """ Swap specie index positions """ speciea = self.index(a) specieb = self.index(b) idx_a = (self._specie == speciea).nonzero()[0] idx_b = (self._specie == specieb).nonzero()[0] atoms = self.copy() atoms._atom[speciea], atoms._atom[specieb] = atoms._atom[specieb], atoms._atom[speciea] atoms._specie[idx_a] = specieb atoms._specie[idx_b] = speciea atoms._update_orbitals() return atoms
[docs] def append(self, other): """ Append `other` to this list of atoms and return the appended version Parameters ---------- other : Atoms or Atom new atoms to be added Returns ------- Atoms merging of this objects atoms and the `other` objects atoms. """ if not isinstance(other, Atoms): other = Atoms(other) atoms = self.copy() spec = np.copy(other._specie) for i, atom in enumerate(other.atom): try: s = atoms.index(atom) except KeyError: s = len(atoms.atom) atoms._atom.append(atom) spec = np.where(other._specie == i, s, spec) atoms._specie = np.concatenate((atoms._specie, spec)) atoms._update_orbitals() 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]] atoms._update_orbitals() 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) atoms._update_orbitals() return atoms
def __str__(self): """ Return the `Atoms` in str """ s = self.__class__.__name__ + '{{species: {0},\n'.format(len(self._atom)) for a, idx in self.iter(True): s += ' {1}: {0},\n'.format(len(idx), str(a).replace('\n', '\n ')) return s + '}' def __len__(self): """ Return number of atoms in the object """ return len(self._specie)
[docs] def iter(self, species=False): """ Loop on all atoms This iterator may be used in two contexts: 1. `species` is ``False``, this is the slowest method and will yield the `Atom` per contained atom. 2. `species` is ``True``, which yields a tuple of `(Atom, list)` where ``list`` contains all indices of atoms that has the `Atom` specie. This is much faster than the first option. Parameters ---------- species : bool, optional If ``True`` loops only on different species and yields a tuple of (Atom, list) Else yields the atom for the equivalent index. """ if species: for s, atom in enumerate(self._atom): yield atom, (self.specie == s).nonzero()[0] else: for s in self.specie: yield self._atom[s]
def __iter__(self): """ Loop on all atoms with the same specie in order of atoms """ for atom in self.iter(): yield atom 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): sl = key.indices(len(self)) return [self.atom[self._specie[s]] for s in range(sl[0], sl[1], sl[2])] elif isinstance(key, Integral): return self.atom[self._specie[key]] return [self.atom[i] for i in self._specie[_a.asarrayi(key).ravel()]] def __setitem__(self, key, value): """ Overwrite an `Atom` object corresponding to the key(s) """ # Convert to array if isinstance(key, slice): sl = key.indices(len(self)) key = _a.arangei(sl[0], sl[1], sl[2]) else: key = _a.asarrayi(key).ravel() if len(key) == 0: if value not in self: self._atom.append(value) return # Create new atoms object to iterate other = Atoms(value, na=len(key)) # Append the new Atom objects for atom, s_i in other.iter(True): if atom not in self: self._atom.append(atom) self._specie[key[s_i]] = self.index(atom) self._update_orbitals()
[docs] def replace(self, index, atom): """ Replace all atomic indices `index` with the atom `atom` (in-place) This is the preferred way of replacing atoms in geometries. Parameters ---------- index : list of int or Atom the indices of the atoms that should be replaced by the new atom. If an `Atom` is passed, this routine defers its call to `replace_atom`. atom : Atom the replacement atom. """ if isinstance(index, Atom): self.replace_atom(index, atom) return if not isinstance(atom, Atom): raise ValueError(self.__class__.__name__ + '.replace requires input arguments to ' 'be of the class Atom') index = _a.asarrayi(index).ravel() # Be sure to add the atom if atom not in self.atom: self._atom.append(atom) # Get specie index of the atom specie = self.index(atom) # Loop unique species and check that we have the correct number of orbitals for ius in np.unique(self._specie[index]): a = self._atom[ius] if a.no != atom.no: a1 = ' ' + str(a).replace('\n', '\n ') a2 = ' ' + str(atom).replace('\n', '\n ') info('Substituting atom\n{}\n->\n{}\nwith a different number of orbitals!'.format(a1, a2)) self._specie[index] = specie # Update orbital counts... self._update_orbitals()
[docs] def replace_atom(self, atom_from, atom_to): """ Replace all atoms equivalent to `atom_from` with `atom_to` (in-place) I.e. this is the preferred way of adapting all atoms of a specific type with another one. If the two atoms does not have the same number of orbitals a warning will be raised. Parameters ---------- atom_from : Atom the atom that should be replaced, if not found in the current list of atoms, nothing will happen. atom_to : Atom the replacement atom. Raises ------ UserWarning : if the atoms does not have the same number of orbitals. """ if not isinstance(atom_from, Atom): raise ValueError(self.__class__.__name__ + '.replace_atom requires input arguments to ' 'be of the class Atom') if not isinstance(atom_to, Atom): raise ValueError(self.__class__.__name__ + '.replace_atom requires input arguments to ' 'be of the class Atom') update_orbitals = False for i, atom in enumerate(self.atom): if atom == atom_from: if atom.no != atom_to.no: a1 = ' ' + str(atom).replace('\n', '\n ') a2 = ' ' + str(atom_to).replace('\n', '\n ') info('Replacing atom\n{}\n->\n{}\nwith a different number of orbitals!'.format(a1, a2)) update_orbitals = True self._atom[i] = atom_to if update_orbitals: # Update orbital counts... self._update_orbitals()
[docs] def hassame(self, other, R=True): """ True if the contained atoms are the same in the two lists Notes ----- This does not necessarily mean that the order, nor the number of atoms are the same. Parameters ---------- other : Atoms the list of atoms to check against R : bool, optional if True also checks that the orbital radius are the same See Also -------- equal : explicit check of the indices *and* the contained atoms """ if len(self.atom) != len(other.atom): return False for A in self.atom: is_in = False for B in other.atom: if A.equal(B, R): is_in = True break if not is_in: return False return True
[docs] def equal(self, other, R=True): """ True if the contained atoms are the same in the two lists (also checks indices) Parameters ---------- other : Atoms the list of atoms to check against R : bool, optional if True also checks that the orbital radius are the same See Also -------- hassame : only check whether the two atoms are contained in both """ if len(self.atom) > len(other.atom): for iA, A in enumerate(self.atom): is_in = -1 for iB, B in enumerate(other.atom): if A.equal(B, R): is_in = iB break if is_in == -1: return False # We should check that they also have the same indices if not np.all(np.nonzero(self.specie == iA)[0] \ == np.nonzero(other.specie == is_in)[0]): return False else: for iB, B in enumerate(other.atom): is_in = -1 for iA, A in enumerate(self.atom): if B.equal(A, R): is_in = iA break if is_in == -1: return False # We should check that they also have the same indices if not np.all(np.nonzero(other.specie == iB)[0] \ == np.nonzero(self.specie == is_in)[0]): return False return True
def __eq__(self, b): """ Returns true if the contained atoms are the same """ return self.equal(b) # 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']