Ionic conductivity from ams.rkf trajectory

First, an ams.rkf trajectory with ions in the system needs to be calculated, as is done, for example, in the ionic conductivity Tutorial. Next, the ions in the system and their respective charges need to be identified.

Example usage: (Download get_charged_ions.py)

$AMSBIN/amspython get_charged_ions.py /path/to/ams.rkf
import sys
import os
import numpy
from scm.plams import AMSJob, Settings, Units, AMSAnalysisJob
from scm.plams import to_rdmol, init, finish


def main(filename):
    """
    Main functionality (charge assignment to ions)
    """
    # Get the molecular system
    job = AMSJob.load_external(filename)
    mol = job.molecule
    elements = [at.symbol for at in mol.atoms]

    # Assign atomic charges
    charges = mol.guess_atomic_charges(adjust_to_systemcharge=False)
    charges = add_metal_charges(mol, charges)

    # Find ions
    molindices = mol.get_molecule_indices()
    ions = {}
    ioncharges = {}
    nions = {}
    formulas = {}
    for imol, atoms in enumerate(molindices):
        submol = mol.get_fragment(atoms)
        label = submol.label()
        q = sum([charges[i] for i in atoms])
        if abs(q) > 1e-10:
            if not label in ions.keys():
                ions[label] = []
                ioncharges[label] = q
                nions[label] = 0
                formulas[label] = submol.get_formula()
            ions[label] += atoms
            nions[label] += 1
    for k, q in ioncharges.items():
        print(formulas[k], q, nions[k])

    return ions, ioncharges


def add_metal_charges(mol, charges):
    """
    Assign charges to the metal ions
    """
    system_charge = mol.properties.charge if not isinstance(mol.properties.charge, Settings) else 0.0
    if system_charge == sum(charges):
        return charges
    dq = system_charge - sum(charges)
    # print ('Charge difference: ',dq)

    elements = [at.symbol for at in mol.atoms]
    metals = [i for i, at in enumerate(mol.atoms) if at.is_metallic]
    # print ('Metals: ',metals)

    electron_affinities, ionization_energies = read_ea_ie()
    chi0 = {iat: 0.5 * (electron_affinities[elements[iat]] + ionization_energies[elements[iat]]) for iat in metals}
    J0 = {iat: ionization_energies[elements[iat]] - electron_affinities[elements[iat]] for iat in metals}
    Jc = 1.0

    # Set up the matrix (A) and vector b, to then solve Ax = b
    # The last element of the vector (and last row of A) hold the constraint sum(x_i) = Q_tot
    matrix = numpy.zeros((len(metals) + 1, len(metals) + 1))
    b = numpy.ones(len(metals) + 1)
    for i, iat in enumerate(metals):
        matrix[i, i] = J0[iat]
        b[i] = chi0[iat]
    matrix[-1, :-1] = 1.0
    matrix[:-1, -1] = 1.0
    b[-1] = dq
    # print ('Matrix:')
    # print (matrix)
    # print ('b: ',b)

    # Solve Ax+b
    mcharges = numpy.linalg.solve(matrix, b)
    mcharges_int = [round(q) for q in mcharges]
    if sum(mcharges_int) != dq:
        print([(elements[iat], mcharges[i]) for i, iat in enumerate(metals)])
        raise Exception("Predicted charges non-integer!")
    for i, iat in enumerate(metals):
        charges[iat] = mcharges_int[i]
    return charges


def read_ea_ie():
    """
    Read in the electron affinities and ionization energies from file
    """
    electron_affinities = {}
    ionization_energies = {}
    for el, l in data.items():
        electron_affinities[el] = data[el][0]
        ionization_energies[el] = data[el][1]
    return electron_affinities, ionization_energies


# Element:[     ElectronAffinity,     IonizationEnergy]
data = {
    "Li": [0.0227110811, 0.1981523452],
    "Na": [0.0201386286, 0.1888547667],
    "Al": [0.0162064511, 0.2199814425],
    "Si": [0.0508978112, 0.2995804744],
    "Sc": [0.0069088726, 0.2411123028],
    "Ti": [0.0029031965, 0.2509243718],
    "V": [0.0192933941, 0.2479109274],
    "Cr": [0.0244750486, 0.2486826632],
    "Fe": [0.0059901395, 0.2903931438],
    "Co": [0.0242913020, 0.2896214081],
    "Ni": [0.0424822164, 0.2807648214],
    "Cu": [0.0451281676, 0.2839252631],
    "Ga": [0.0110247967, 0.2204591837],
    "Ge": [0.0496115849, 0.2903196452],
    "Rb": [0.0171986828, 0.1535019187],
    "Y": [0.0112820419, 0.2284705360],
    "Zr": [0.0156552112, 0.2437950033],
    "Nb": [0.0328171447, 0.2483886686],
    "Mo": [0.0274149943, 0.2606261929],
    "Tc": [0.0202121272, 0.2675350654],
    "Ru": [0.0385867883, 0.2705117605],
    "Rh": [0.0417839793, 0.2741131941],
    "Pd": [0.0204693725, 0.3063790990],
    "Ag": [0.0478476175, 0.2784128648],
    "In": [0.0110247967, 0.2126315781],
    "Sn": [0.0440991866, 0.2698870221],
    "Sb": [0.0393217747, 0.3175141436],
    "Cs": [0.0173456801, 0.1431018606],
    "La": [0.0183746611, 0.2049509698],
    "Ce": [0.0183746611, 0.2035544955],
    "Ta": [0.0118332817, 0.2899521520],
    "W": [0.0299506976, 0.2932595910],
    "Re": [0.0055123983, 0.2895846587],
    "Os": [0.0404242544, 0.3197191029],
    "Ir": [0.0575126892, 0.3344188318],
    "Au": [0.0848541849, 0.3390492464],
    "Tl": [0.0073498644, 0.2244648598],
    "Pb": [0.0132297560, 0.2725697226],
    "Bi": [0.0347648588, 0.2678658093],
    "Po": [0.0698237121, 0.3093190448],
}

if __name__ == "__main__":
    if len(sys.argv) == 1:
        print("Usage: amspython get_chargee_ions.py path/to/ams.rkf")
        sys.exit(0)
    filename = sys.argv[1]

    ions, ioncharges = main(filename)

    outfile = open("charges.in", "w")
    for k, q in ioncharges.items():
        outfile.write("%8.1f: %s\n" % (q, " ".join(["%i" % (iat) for iat in ions[k]])))
    outfile.close()

Running the above script results in a small input file (charges.in) containing the ion information. This input file can be used in the next script, to compute the ionic conductivity.

Example usage: (Download get_ionic_conductivity.py)

$AMSBIN/amspython get_ionic_conductivity.py /path/to/ams.rkf <charges.in
import sys
import os
import numpy
from scm.plams import AMSJob, Settings, Units, AMSAnalysisJob
from scm.plams import to_rdmol, init, finish


def main(filename, chargelines):
    """
    The main body of the script
    """
    # Get the molecular system
    job = AMSJob.load_external(filename)
    mol = job.molecule
    elements = [at.symbol for at in mol.atoms]

    # Read the temperature from KF
    T = job.results.readrkf("MDResults", "MeanTemperature")
    kBT = Units.constants["Boltzmann"] * T
    print("Average temperaturs %f K" % (T))

    # Read the charges from the input file
    ions = {}
    ioncharges = {}
    nions = {}
    formulas = {}
    for line in chargelines:
        words = line.split()
        if len(words) == 0:
            continue
        q = float(line.split(":")[0])
        atoms = [int(iat) for iat in line.split(":")[-1].split()]
        submol = mol.get_fragment(atoms)
        label = submol.label()
        if not label in ions.keys():
            ions[label] = []
            ioncharges[label] = q * Units.constants["electron_charge"]
            nions[label] = 0
            formulas[label] = submol.separate()[0].get_formula()
        ions[label] += atoms
        nions[label] += len(atoms)

    # Compute diffusion coefficient for each ion
    init()
    diffusion_coeffs = {}
    for label, atoms in ions.items():
        s = Settings()
        s.input.Task = "MeanSquareDisplacement"
        s.input.TrajectoryInfo.Trajectory.KFFilename = filename
        atsettings = [iat + 1 for iat in atoms]
        s.input.MeanSquareDisplacement.Atoms.Atom = atsettings

        job = AMSAnalysisJob(settings=s)
        results = job.run()
        D = results._kf.read("Slope(1)", "Final")
        diffusion_coeffs[label] = D
        units = results._kf.read("Slope(1)", "Final(units)")
        print(formulas[label], D, units)
    finish()

    # Compute the number density for each ion
    rho = {}
    for label, ni in nions.items():
        rho[label] = ni / mol.unit_cell_volume(unit="m")

    # Compute the ionic conductivity
    sigma = 0.0
    for label, D in diffusion_coeffs.items():
        s = ioncharges[label] ** 2 * rho[label] * D / kBT
        sigma += s
    return sigma


if __name__ == "__main__":
    if len(sys.argv) == 1 or sys.stdin.isatty():
        print("Usage: amspython get_ionic_conductivity.py path/to/ams.rkf < charges.in")
        sys.exit(0)
    chargelines = sys.stdin.readlines()
    filename = sys.argv[1]
    sigma = main(filename, chargelines)
    print("Ionic conductivity: %20.10e Siemens/m" % (sigma))