"""Contains fundamental constants in atomic units. Copyright (C) 2013, Joshua More and Michele Ceriotti This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Classes: Constants: Class whose members are fundamental constants. Elements: Class which contains the mass of different elements Units: Class which contains the methods needed to transform between different systems of units. """ import re from ipi.utils.messages import verbosity, info __all__ = ['Constants', 'Elements', 'unit_to_internal', 'unit_to_user'] class Constants: """Class whose members are fundamental constants. Attributes: kb: Boltzmann constant. hbar: Reduced Planck's constant. amu: Atomic mass unit. """ kb = 1.0 hbar = 1.0 amu = 1822.8885 class Elements(dict): """Class which contains the mass of different elements. Attributes: mass_list: A dictionary containing the masses of different elements. Has the form {"label": Mass in a.m.u.}. Note that the generic "X" label is assumed to be an electron. """ mass_list={ "X" : 1.0000/Constants.amu, "H" : 1.00794, "D" : 2.0141, "Z" : 1.382943, #an interpolated H-D atom, based on y=1/sqrt(m) scaling "H2" : 2.0160, "He" : 4.002602, "Li" : 6.9410, "Be" : 9.012182, "B" : 10.811, "C" : 12.0107, "N" : 14.00674, "O" : 15.9994, "F" : 18.998403, "Ne" : 20.1797, "Na" : 22.989770, "Mg" : 24.3050, "Al" : 26.981538, "Si" : 28.0855, "P" : 30.973761, "S" : 32.066, "Cl" : 35.4527, "Ar" : 39.9480, "K" : 39.0983, "Ca" : 40.078, "Sc" : 44.955910, "Ti" : 47.867, "V" : 50.9415, "Cr" : 51.9961, "Mn" : 54.938049, "Fe" : 55.845, "Co" : 58.933200, "Ni" : 58.6934, "Cu" : 63.546, "Zn" : 65.39, "Ga" : 69.723, "Ge" : 72.61, "As" : 74.92160, "Se" : 78.96, "Br" : 79.904, "Kr" : 83.80, "Rb" : 85.4678, "Sr" : 87.62, "Y" : 88.90585, "Zr" : 91.224, "Nb" : 92.90638, "Mo" : 95.94, "Tc" : 98, "Ru" : 101.07, "Rh" : 102.90550, "Pd" : 106.42, "Ag" : 107.8682, "Cd" : 112.411, "In" : 114.818, "Sn" : 118.710, "Sb" : 121.760, "Te" : 127.60, "I" : 126.90447, "Xe" : 131.29, "Cs" : 132.90545, "Ba" : 137.327, "La" : 138.9055, "Ce" : 140.166, "Pr" : 140.90765, "Nd" : 144.24, "Pm" : 145, "Sm" : 150.36, "Eu" : 151.964, "Gd" : 157.25, "Tb" : 158.92534, "Dy" : 162.50, "Ho" : 164.93032, "Er" : 167.26, "Tm" : 168.93241, "Yb" : 173.04, "Lu" : 174.967, "Hf" : 178.49, "Ta" : 180.9479, "W" : 183.84, "Re" : 186.207, "Os" : 190.23, "Ir" : 192.217, "Pt" : 195.078, "Au" : 196.96655, "Hg" : 200.59, "Tl" : 204.3833, "Pb" : 207.2, "Bi" : 208.98038, "Po" : 209, "At" : 210, "Rn" : 222, "Fr" : 223, "Ra" : 226, "Ac" : 227, "Th" : 232.0381, "Pa" : 231.03588, "U" : 238.0289, "Np" : 237, "Pu" : 244, "Am" : 243, "Cm" : 247, "Bk" : 247, "Cf" : 251, "Es" : 252, "Fm" : 257, "Md" : 258, "No" : 259, "Lr" : 262, "Rf" : 267, "Db" : 268, "Sg" : 269, "Bh" : 270, "Hs" : 269, "Mt" : 278, "Ds" : 281, "Rg" : 280, "Cn" : 285, "Uut" : 286, "Fl" : 289, "Uup" : 288, "Lv" : 293, "Uus" : 294, "Uuo" : 294 } @classmethod def mass(cls, label): """Function to access the mass_list attribute. Note that this does not require an instance of the Elements class to be created, as this is a class method. Therefore using Elements.mass(label) will give the mass of the element with the atomic symbol given by label. Args: label: The atomic symbol of the atom whose mass is required. Returns: A float giving the mass of the atom with atomic symbol label. """ try: return cls.mass_list[label]*Constants.amu except KeyError: info("Unknown element given, you must specify the mass", verbosity.low) return -1.0 # these are the conversion FROM the unit stated to internal (atomic) units UnitMap = { "undefined": { "" : 1.00 }, "energy": { "" : 1.00, "atomic_unit" : 1.00, "electronvolt" : 0.036749326, "j/mol" : 0.00000038087989, "cal/mol" : 0.0000015946679, "kelvin" : 3.1668152e-06 }, "temperature": { "" : 1.00, "atomic_unit" : 1.00, "kelvin" : 3.1668152e-06 }, "time": { "" : 1.00, "atomic_unit" : 1.00, "second" : 4.1341373e+16 }, "frequency" : { # NB Internally, ANGULAR frequencies are used. "" : 1.00, "atomic_unit" : 1.00, "inversecm" : 4.5563353e-06, "hertz*rad" : 2.4188843e-17, "hertz" : 1.5198298e-16 }, "ms-momentum" : { # TODO fill up units here (mass-scaled momentum) "" : 1.00, "atomic_unit" : 1.00 }, "length" : { "" : 1.00, "atomic_unit" : 1.00, "angstrom" : 1.8897261, "meter" : 1.8897261e+10 }, "volume" : { "" : 1.00, "atomic_unit" : 1.00, "angstrom3" : 6.748334231, }, "velocity": { "" : 1.00, "atomic_unit" : 1.00, "m/s" : 4.5710289e-7 }, "momentum": { "" : 1.00, "atomic_unit" : 1.00 }, "mass": { "" : 1.00, "atomic_unit" : 1.00, "dalton" : 1.00*Constants.amu, "electronmass" : 1.00 }, "pressure" : { "" : 1.00, "atomic_unit" : 1.00, "bar" : 3.398827377e-9, "atmosphere" : 3.44386184e-9, "pascal" : 3.398827377e-14 }, "density" : { "" : 1.00, "atomic_unit" : 1.00, "g/cm3" : 162.67263 }, "force" : { "" : 1.00, "atomic_unit" : 1.00, "newton" : 12137805 } } # a list of magnitude prefixes UnitPrefix = { "" : 1.0, "yotta" : 1e24, "zetta" : 1e21, "exa" : 1e18, "peta" : 1e15, "tera" : 1e12, "giga" : 1e9, "mega" : 1e6, "kilo" : 1e3, "milli" : 1e-3, "micro" : 1e-6, "nano" : 1e-9, "pico" : 1e-12, "femto" : 1e-15, "atto" : 1e-18, "zepto" : 1e-21, "yocto" : 1e-24 } # builds a RE to match prefix and split out the base unit UnitPrefixRE = "" for key in UnitPrefix: UnitPrefixRE = UnitPrefixRE + key + "|" UnitPrefixRE = " *(" + UnitPrefixRE[1:] + ")(.*) *" UnitPrefixRE = re.compile(UnitPrefixRE) ######################################################################## # Atomic units are used EVERYWHERE internally. In order to quickly # # interface with any "outside" unit, we set up a simple conversion # # library. # ######################################################################## def unit_to_internal(family, unit, number): """Converts a number of given dimensions and units into internal units. Args: family: The dimensionality of the number. unit: The units 'number' is originally in. number: The value of the parameter in the units 'unit'. Returns: The number in internal units. Raises: ValueError: Raised if the user specified units aren't given in the UnitMap dictionary. IndexError: Raised if the programmer specified dimensionality for the parameter isn't in UnitMap. Shouldn't happen, for obvious reasons. TypeError: Raised if the prefix is correct, but the base unit is not, in the user specified unit string. """ if not (family == "number" or family in UnitMap): raise IndexError(family + " is an undefined units kind.") if family == "number": return number if unit == "": prefix = "" base = "" else: m = UnitPrefixRE.match(unit); if m is None: raise ValueError("Unit " + unit + " is not structured with a prefix+base syntax.") prefix = m.group(1) base = m.group(2) if not prefix in UnitPrefix: raise TypeError(prefix + " is not a valid unit prefix.") if not base in UnitMap[family]: raise TypeError(base + " is an undefined unit for kind " + family + ".") return number*UnitMap[family][base]*UnitPrefix[prefix] def unit_to_user(family, unit, number): """Converts a number of given dimensions from internal to user units. Args: family: The dimensionality of the number. unit: The units 'number' should be changed to. number: The value of the parameter in internal units. Returns: The number in the user specified units """ return number/unit_to_internal(family, unit, 1.0)