Exemple #1
0
def sort_properties_files():
    """
    Paramters:
    None

    Returns:
    tuple: returns a tuple of lists of lattice constants, bulk moduli,
    interpolated lattice constants and file paths.
    """
    settings = read_settings_file()
    filenames = sorted(glob.glob("property_calculations/properties_*"))
    steps = 1 + 2 * settings['LC_steps']
    LC_list = []
    BulkM_list = []
    N_list = []
    LCi_list = []

    for i in range(0, round(len(filenames) / steps)):
        LCa, LCb, LCc, BulkM, N, LCia, LCib, LCic = find_eq_lc(
            filenames[steps * i:steps * (i + 1)])
        LC_list.append([LCa, LCb, LCc])
        BulkM_list.append(BulkM)
        N_list.append(N)
        LCi_list.append([LCia, LCib, LCic])
        """
        for fname in filenames[steps*i:steps*i+steps]:
            if fname != N:
                os.remove(fname)
        """
    return LC_list, LCi_list, BulkM_list, N_list
Exemple #2
0
def debye_lindemann(a, msd, temp):
    """Calculates the debye temperature and the Lindemann
       criterion. Original cell is assumed to be sc, bcc or fcc.
       Lattice constants in a,b and c may be different.
    Parameters:
    a (obj): a is an atoms object of class defined in ase.
    msd (float): mean square displacment
    temp (float): temperature

    Returns
    list: list of debye temperature and lindemann criterion.
    """
    s = read_settings_file()
    debye = math.sqrt(9 * units._hbar**2 * temp /
                      (units._k * a.get_masses()[0] * units._amu * msd) *
                      units.m**2)
    z = s['supercell_size']
    n = len(a) / z**3
    lc = a.get_cell_lengths_and_angles()[0:3]
    if n == 1:
        nnd = min(lc)
    elif n == 2:
        nnd = 1 / 2 * math.sqrt(lc[0]**2 + lc[1]**2 + lc[2]**2)
    elif n == 4:
        if np.max(lc) != np.min(lc):  # all values in lc are not the same
            lc = np.delete(lc, np.argwhere(lc == max(lc)))
        nnd = 1 / 2 * math.sqrt(lc[0]**2 + lc[1]**2)
    else:
        nnd = 9999
    lindemann = math.sqrt(msd) / nnd
    return debye, lindemann
Exemple #3
0
def lattice_constants(a):
    """ Calculates the lattice constant of a materialself.

    Parameters:
    a (obj): a is an atoms object of class defined in ase.

    Returns:
    list: returns the lattice_constants, in the 3 dimensions.
    """

    s = read_settings_file()['supercell_size']
    lc = list(a.get_cell_lengths_and_angles())
    return [lc[0] / s, lc[1] / s, lc[2] / s]
    def test_read_settings(self):
        # create empty json file
        with open("empty_test.json", "w+") as f:
            f.write("{\n    \"koko\": 1000 \n}")
        with open("empty_test.json") as f:
            data = json.load(f)
        with self.assertRaises(KeyError):
            data["temperatur"]

        # Now test read_settings_file function
        data = read_settings.read_settings_file("empty_test.json")
        self.assertTrue(data["time_step"], 5)
        self.assertTrue(data["max_steps"], 200)
        self.assertTrue(data["temperature"], 300)
        self.assertTrue(data["ensemble"], "NVE")
        self.assertTrue(data["friction"], 0.001)
        self.assertTrue(data["decimals"], 5)
        os.remove("empty_test.json")
Exemple #5
0
def calc_properties(a_old, a, id, d, ma):
    """Calculates prioperties and writes them in a file.

    Parameters:
    a_old (obj): a_old is an atoms object from clas defined from ase.
                it's the old atom that needs to be updated.
    a (obj): a is an atoms object from clas defined from ase.
            it's the new updated atom obj for MD molecular dyanimcs.
    id (str):
    d (int):
    ma (boolean):
    Returns: None

    """
    f = open("property_calculations/properties_" + id + ".txt", "r")

    epot, ekin, etot, temp = energies_and_temp(a)
    msd = meansquaredisp(a, a_old)
    settings = read_settings_file()
    ln = sum(1 for line in f)
    time = settings['time_step'] * settings['interval'] * (ln - 6)
    selfd = self_diff(a, msd, time)
    lc = lattice_constants(a)
    vol, pr = volume_pressure(a)
    f.close()

    file = open("property_calculations/properties_" + id + ".txt", "a+")
    file.write(
        ss(time, d) + ss(epot, d) + ss(ekin, d) + ss(etot, d) + ss(temp, 2) +
        ss(msd, d))
    file.write(ss(selfd, d) + ss(lc[0], 3) + ss(lc[1], 3) + ss(lc[2], 3))
    file.write(ss(vol, 3) + ss(pr, d))
    if ma:
        debye, linde = debye_lindemann(a, msd, temp)
        file.write(ss(debye, 2) + ss(linde, d))

    file.write("\n")
    file.close()
    return
Exemple #6
0
def specific_heat(temp_store, N, atoms):
    """Calculates the specific heat for a material.
    Given by the formula: (E[T²] - E[T]²)/ E[T]² = 3*2^-1*N^-1*(1-3*N*Kb*2^-1*Cv^-1).
    Where Kb is boltzmansconstant, N is the total number of atoms, T is temperature and Cv the specific heat.
    E[A(t)] calculates the expectation value of A, which can in this case be seen as a time average for the
    phase variable A(t).

    Parameters:
    temp_store (list): The list over all intantaneous temperatures of a material once MD has been run.
    N (int): The total number of atoms in the material.

    Returns:
    float: specific heat is returned (J/(K*Kg))
    """
    if len(temp_store) == 0:
        raise ValueError("temp_store is empty, invalid value.")
    steps = len(temp_store)
    z = sum(atoms.get_masses()) * units._amu  # total mass: atomic units to kg
    # Set M = (E[T²] - E[T]²)/ E[T]²
    ET = sum(temp_store) / steps
    ET2 = sum(np.array(temp_store)**2) / steps
    M = (ET2 - ET**2) / ET**2
    settings = read_settings_file()
    N = N / settings['supercell_size']**3
    #print("M:", M)
    #print("N:", N)
    #print("T:", temp_store)
    #print(sum(np.array(temp_store)**2))
    #print("ET:", ET)
    #print("ET2:", ET2)
    #Cv1 = -9*N*units.kB/(4*N*M-6)/z*units._e * settings['supercell_size']**3 # specific heat J/(K*Kg)
    Cv2 = ((9 * ET**2 * N * units._k) /
           (ET**2 *
            (6 + 4 * N) - 4 * N * ET2)) / z * settings['supercell_size']**3
    #print("Cv1:", Cv1)
    #print("Cv2:", Cv2)
    return Cv2
Exemple #7
0
from ase.visualize import view
from ase.build import bulk
import md
from read_settings import read_settings_file
import numpy as np
import copy

# http://www-ferp.ucsd.edu/LIB/PROPS/PANOS/cu.html
# properties of copper
atoms_l = []
#atoms_l.append(bulk('Ar', 'fcc', a=5.26, cubic=True))
atoms_l.append(bulk('Cu', 'fcc', a=3.6, cubic=True))
#atoms = bulk('Ar', 'fcc', a=5.26, cubic=True)
#atoms = bulk('Cu', 'fcc', a=3.6, cubic=True)

settings = read_settings_file('acc_test_setting.json')

atoms_list = []
for atoms in atoms_l:
    if settings['vol_relax']:
        cell = np.array(atoms.get_cell())
        P = settings['LC_steps']
        for i in range(-P, 1 + P):
            atoms_v = copy.deepcopy(atoms)
            atoms_v.set_cell(cell * (1 + i * settings['LC_mod']))
            atoms_list.append(atoms_v)
    else:
        atoms_list.append(atoms)

print(atoms_list)
Exemple #8
0
def main():
    # don't allow Python libraries to start spanning threads
    # over the available cores
    supercomputer_init()

    # set up variables for parallelization
    comm = MPI.COMM_WORLD
    rank = comm.Get_rank()
    size = comm.Get_size()

    # read materials from settings file
    settings = read_settings_file()
    mp_properties = read_mp_properties(settings['materials'])

    # try to create folder 'property_calculations'
    # if it already exists, continue with the program
    try:
        os.mkdir('property_calculations')
    except:
        pass

    # try to create folder 'trajectory_files'
    # if it already exists, continue with the program
    try:
        os.mkdir('trajectory_files')
    except:
        pass

    # create one list of all atom objects in data-file
    atoms_list = []
    for cif in mp_properties['cif']:
        f = open('tmp'+str(rank)+'.cif', 'w+')
        f.write(cif)
        f.close()
        atoms = ase.io.read('tmp'+str(rank)+'.cif')
        lengths = atoms.get_cell_lengths_and_angles()[0:3]
        angles = atoms.get_cell_lengths_and_angles()[3:6]
        if settings['cubic_only']:
            if len(set(lengths)) == 1 and len(set(angles)) == 1 and angles[0] == 90:
                if settings['vol_relax']:
                    cell = np.array(atoms.get_cell())
                    P = settings['LC_steps']
                    for i in range(-P,1+P):
                        atoms_v = copy.deepcopy(atoms)
                        atoms_v.set_cell(cell*(1+i*settings['LC_mod']))
                        atoms_list.append(atoms_v)
                else:
                    atoms_list.append(atoms)
        else:
            if settings['vol_relax']:
                cell = np.array(atoms.get_cell())
                P = settings['LC_steps']
                for i in range(-P,1+P):
                    atoms_v = copy.deepcopy(atoms)
                    atoms_v.set_cell(cell*(1+i*settings['LC_mod']))
                    atoms_list.append(atoms_v)
            else:
                atoms_list.append(atoms)
    print("Created atoms list of length " + str(len(atoms_list)))
    os.remove("tmp"+str(rank)+".cif")

    # Run the molecular dynamics in parallell (might want to
    # improve it)
    if rank == 0:
        jobs = np.arange(0, len(atoms_list), dtype=np.int)
        job_array = np.array_split(jobs, size)
        #print("we have", size, " processes.")
        for i in range(0, size):
            comm.isend(len(job_array[i]), dest=i, tag=i)
            comm.Isend([job_array[i],MPI.INT], dest=i, tag=i)

    # how do I send in the correct atoms-object to md_run?
    l = comm.recv(source=0, tag=rank)
    data = np.ones(l,dtype=np.int)
    comm.Recv([data,MPI.INT],source=0, tag=rank)
    for id in data:
        #print("ID: ", id)
        #print(atoms)
        try:
            md.run_md(atoms_list[id], str(id).zfill(4), 'settings.json')
        except Exception as e:
            print("Run broke!:"+str(e))
            print("Happened for ID:" + str(id).zfill(4))
    comm.Barrier()
Exemple #9
0
def run_md(atoms, id, settings_fn):
    """The function does Molecular Dyanamic simulation (MD) on a material, given by argument atoms.

    Parameters:
    atoms (obj): an atoms object defined by class in ase. This is the material which MD
    will run on.
    id (int): an identifying number for the material.

    Returns:
    obj:atoms object defined in ase, is returned.
    """
    # Read settings
    settings = read_settings_file(settings_fn)
    initial_unitcell_atoms = copy.deepcopy(atoms)
    # Scale atoms object, cubic
    size = settings['supercell_size']
    atoms = atoms * size * (1, 1, 1)
    # atoms = atoms * (size,size,size)
    #print(atoms.get_chemical_symbols())
    N = len(atoms.get_chemical_symbols())

    # Use KIM for potentials from OpenKIM
    use_kim = settings['use_kim']

    # Use Asap for a huge performance increase if it is installed
    use_asap = True

    # Create a copy of the initial atoms object for future reference
    old_atoms = copy.deepcopy(atoms)

    # Describe the interatomic interactions with OpenKIM potential
    if use_kim:  # use KIM potential
        atoms.calc = KIM(
            "LJ_ElliottAkerson_2015_Universal__MO_959249795837_003")
    else:  # otherwise, default to asap3 LennardJones
        atoms.calc = LennardJones([18], [0.010323], [3.40],
                                  rCut=6.625,
                                  modified=True)

    # Set the momenta corresponding to temperature from settings file
    MaxwellBoltzmannDistribution(atoms, settings['temperature'] * units.kB)

    # Select integrator
    if settings['ensemble'] == "NVE":
        from ase.md.verlet import VelocityVerlet
        dyn = VelocityVerlet(atoms, settings['time_step'] * units.fs)

    elif settings['ensemble'] == "NVT":
        from ase.md.langevin import Langevin
        dyn = Langevin(atoms, settings['time_step'] * units.fs,
                       settings['temperature'] * units.kB,
                       settings['friction'])

    interval = settings['interval']

    # Creates trajectory files in directory trajectory_files
    traj = Trajectory("trajectory_files/" + id + ".traj", 'w', atoms)
    dyn.attach(traj.write, interval=interval)

    # Number of decimals for most calculated properties.
    decimals = settings['decimals']
    # Boolean indicating if the material is monoatomic.
    monoatomic = len(set(atoms.get_chemical_symbols())) == 1

    # Calculation and writing of properties
    properties.initialize_properties_file(atoms, initial_unitcell_atoms, id,
                                          decimals, monoatomic)
    dyn.attach(properties.calc_properties, 100, old_atoms, atoms, id, decimals,
               monoatomic)

    # unnecessary, used for logging md runs
    # we should write some kind of logger for the MD
    def logger(a=atoms):  # store a reference to atoms in the definition.
        """Function to print the potential, kinetic and total energy."""
        epot = a.get_potential_energy() / len(a)
        ekin = a.get_kinetic_energy() / len(a)
        t = ekin / (1.5 * units.kB)
        print('Energy per atom: Epot = %.3feV  Ekin = %.3feV (T=%3.0fK)  '
              'Etot = %.3feV' % (epot, ekin, t, epot + ekin))

    # Running the dynamics
    dyn.attach(logger, interval=interval)
    #logger()
    #dyn.run(settings['max_steps'])
    # check for thermal equilibrium
    counter = 0
    equilibrium = False
    for i in range(round(settings['max_steps'] /
                         settings['search_interval'])):  # hyperparameter
        epot, ekin_pre, etot, t = properties.energies_and_temp(atoms)
        # kör steg som motsvarar säg 5 fs
        dyn.run(settings['search_interval'])  # hyperparamter
        epot, ekin_post, etot, t = properties.energies_and_temp(atoms)
        #print(abs(ekin_pre-ekin_post) / math.sqrt(N))
        #print(counter)
        if (abs(ekin_pre - ekin_post) / math.sqrt(N)) < settings['tolerance']:
            counter += 1
        else:
            counter = 0
        if counter > settings['threshold']:  # hyperparameter
            print("reached equilibrium")
            equilibrium = True
            break

    if equilibrium:
        dyn.run(settings['max_steps'])
        properties.finalize_properties_file(atoms, id, decimals, monoatomic)
    else:
        properties.delete_properties_file(id)
        raise RuntimeError("MD did not find equilibrium")
    return atoms
Exemple #10
0
def extract():
    """
    Paramters:
    None

    Returns:
    None
    """
    file = open("property_calculations/collected_data.txt", "w+")
    settings = read_settings_file()
    d = settings['decimals']

    def lj(str, k=d):
        return " " + str.ljust(k + 10)

    file.write(
        lj("Material ID") + lj("Material") + lj("Cohesive energy") +
        lj("MSD") + lj("Self_diff") + lj("Specific heat"))

    if settings['vol_relax']:
        file.write(
            lj("Lattice const a") + lj("Lattice const b") +
            lj("Lattice const c"))
        file.write(lj("Interp LC a") + lj("Interp LC b") + lj("Interp LC c"))
        file.write(lj("Bulk modulus"))

    file.write(lj("Debye", 2) + lj("Lindemann"))
    file.write("\n")

    file.write(
        lj(" ") + lj(" ") + lj("eV/atom") + lj("Å^2") + lj("mm^2/s") +
        lj("J/(K*Kg)"))

    if settings['vol_relax']:
        file.write(
            lj("Å") + lj("Å") + lj("Å") + lj("Å") + lj("Å") + lj("Å") +
            lj("Pa"))

    file.write(lj("K", 2) + lj("1"))
    file.write("\n")

    file.close()
    N_list = glob.glob("property_calculations/properties_*")
    if settings['vol_relax']:
        LC_list, LCi_list, BulkM_list, N_list = sort_properties_files()

    for i, filename in enumerate(sorted(N_list)):
        f = open(filename, "r")
        lines = f.read().split("\n")
        f.close()
        if lines[-4] == 'Time averages:':
            matID = lines[0].split(":")[1]
            mat = lines[2].split()[1]
            Ecoh = lines[-1].split()[0]
            msd = lines[-1].split()[4]
            selfd = lines[-1].split()[5]
            Cv = lines[-1].split()[7]
            file = open("property_calculations/collected_data.txt", "a+")
            file.write(
                lj(matID) + lj(mat) + lj(Ecoh) + lj(msd) + lj(selfd) + lj(Cv))
            if settings['vol_relax']:
                LC = LC_list[i]
                LCi = LCi_list[i]
                BulkM = BulkM_list[i]
                file.write(
                    pr.ss(LC[0], d + 4) + pr.ss(LC[1], d + 4) +
                    pr.ss(LC[2], d + 4))
                file.write(
                    pr.ss(LCi[0], d + 4) + pr.ss(LCi[1], d + 4) +
                    pr.ss(LCi[2], d + 4))
                file.write(pr.ss(BulkM, d + 4))
            if len(lines[-1].split()) > 8:
                debye = lines[-1].split()[8]
                linde = lines[-1].split()[9]
                file.write(pr.ss(debye, d + 4) + pr.ss(linde, d + 4))
            file.write("\n")
            file.close()
    return
Exemple #11
0
def find_eq_lc(fnames):
    """
    Parameters:
    fnames (list): A list of strings of file path/names

    Returns:
    tuple: returns a tuple of lattice constant, bulk modulus,
    filename and interpolated lattice constant.
    """
    LCa = 9999
    LCb = 9999
    LCc = 9999
    Etot = 9999
    N = ""
    E_list = []
    LCa_list = []
    LCb_list = []
    LCc_list = []
    V_list = []
    for name in fnames:
        f = open(name, "r+")
        lines = f.read().split("\n")
        E = float(lines[-1].split()[2])
        # The strucutre of header is always the same.
        l_a = float(lines[11].split()[7])
        l_b = float(lines[11].split()[8])
        l_c = float(lines[11].split()[9])
        E_list.append(E)
        LCa_list.append(l_a)
        LCb_list.append(l_b)
        LCc_list.append(l_c)
        V_list.append(float(lines[11].split()[10]))

        if E < Etot:
            Etot = E
            LCa = l_a
            LCb = l_b
            LCc = l_c
            N = name
        f.close()

    settings = read_settings_file()
    n = LCa_list[0] * LCb_list[0] * LCc_list[0] * settings[
        'supercell_size']**3 / V_list[0]
    oLCa = LCa_list[settings['LC_steps']]  # Original lattice constant a.
    s_list = [x / oLCa for x in LCa_list]
    oV = V_list[settings['LC_steps']]
    p = np.polyfit(s_list, E_list, 2)
    LCia = 0
    LCib = 0
    LCic = 0
    if p[0] <= 0:
        warnings.warn("Dynamically unstable in this range. " + str(fnames))
        B = 0
        LCi = 0
    else:
        i_scaling = -p[1] / (2 * p[0])  # interpolated lattice scaling factor
        LCia = i_scaling * LCa
        LCib = i_scaling * LCb
        LCic = i_scaling * LCc
        E_interp = np.polyval(p, i_scaling)
        V_interp = oV * i_scaling**3
        q = np.polyfit(V_list, E_list, 2)
        B = V_interp * (2 * q[0]) * 160.2  # conversion from ev/Å^3 to GigaPa

    return LCa, LCb, LCc, B, N, LCia, LCib, LCic
Exemple #12
0
def run_md(atoms, id):
    # Read settings
    settings = read_settings_file()

    # Use KIM for potentials from OpenKIM
    use_kim = True

    # Use Asap for a huge performance increase if it is installed
    use_asap = True

    # Create a copy of the initial atoms object for future reference
    old_atoms = copy.deepcopy(atoms)

    # Describe the interatomic interactions with OpenKIM potential
    if use_kim:  # use KIM potential
        atoms.calc = KIM(
            "LJ_ElliottAkerson_2015_Universal__MO_959249795837_003")
    else:  # otherwise, default to asap3 LennardJones
        atoms.calc = LennardJones([18], [0.010323], [3.40],
                                  rCut=6.625,
                                  modified=True)

    # Set the momenta corresponding to temperature from settings file
    MaxwellBoltzmannDistribution(atoms, settings['temperature'] * units.kB)

    # Select integrator
    if settings['ensemble'] == "NVE":
        from ase.md.verlet import VelocityVerlet
        dyn = VelocityVerlet(atoms, settings['time_step'] * units.fs)

    elif settings['ensemble'] == "NVT":
        from ase.md.langevin import Langevin
        dyn = Langevin(atoms, settings['time_step'] * units.fs,
                       settings['temperature'] * units.kB,
                       settings['friction'])

    traj = Trajectory('ar.traj', 'w', atoms)
    dyn.attach(traj.write, interval=1000)

    # Identity number given as func. parameter to keep track of properties
    # Number of decimals for most calculated properties
    decimals = settings['decimals']
    # Calculation and writing of properties
    properties.initialize_properties_file(atoms, id, decimals)
    dyn.attach(properties.calc_properties, 100, old_atoms, atoms, id, decimals)

    # unnecessary, used for logging md runs
    # we should write some kind of logger for the MD
    def logger(a=atoms):  # store a reference to atoms in the definition.
        """Function to print the potential, kinetic and total energy."""
        epot = a.get_potential_energy() / len(a)
        ekin = a.get_kinetic_energy() / len(a)
        t = ekin / (1.5 * units.kB)
        print('Energy per atom: Epot = %.3feV  Ekin = %.3feV (T=%3.0fK)  '
              'Etot = %.3feV' % (epot, ekin, t, epot + ekin))

    # Running the dynamics
    dyn.attach(logger, interval=1000)
    logger()
    dyn.run(settings['max_steps'])

    return atoms
Exemple #13
0
def finalize_properties_file(a, id, d, ma):
    """ Calculates and records the properties of a material.

    Parameters:
    a (obj): Atoms object form ase.
    id (str): a special number identifying the material system.
    d (int): a number for the formatting of file. Give a correct appending
            for strings.
    ma (boolean): ma is a boolean, for True the system is monoatomic.

    Returns: None

    """
    epot = []
    ekin = []
    etot = []
    temp = []
    msd = []
    selfd = []
    pr = []
    debye = []
    linde = []

    settings = read_settings_file()
    f = open("property_calculations/properties_" + id + ".txt", "r")
    f_lines = f.readlines()
    steps = math.floor(settings['max_steps'] / settings['interval'])
    for line in f_lines[-steps:]:
        epot.append(float(line.split()[1]))
        ekin.append(float(line.split()[2]))
        etot.append(float(line.split()[3]))
        temp.append(float(line.split()[4]))
        msd.append(float(line.split()[5]))
        selfd.append(line.split()[6])
        pr.append(float(line.split()[11]))
        if ma:
            debye.append(float(line.split()[12]))
            linde.append(float(line.split()[13]))
    f.close()

    epot_t = sum(epot) / steps
    ekin_t = sum(ekin) / steps
    etot_t = sum(etot) / steps
    temp_t = sum(temp) / steps
    msd_t = sum(msd) / steps
    selfd_t = sum(float(i) for i in selfd[1:]) / (steps - 1)
    pr_t = sum(pr) / steps
    debye_t = sum(debye) / steps
    linde_t = sum(linde) / steps
    Cv = specific_heat(temp, len(a.get_chemical_symbols()), a)

    file = open("property_calculations/properties_" + id + ".txt", "a+")
    file.write("\nTime averages:\n")

    # Help function for formating
    def lj(str, k=d):
        return " " + str.ljust(k + 6)

    file.write(
        lj(" ") + lj("Epot") + lj("Ekin") + lj("Etot") + lj("Temp", 2) +
        lj("MSD"))
    file.write(lj("Self_diff") + lj("Pressure"))
    file.write(lj("Spec_heat"))
    if ma:
        file.write(lj("DebyeT", 2) + lj("Lindemann"))

    file.write("\n")

    file.write(
        lj(" ") + lj("eV/atom") + lj("eV/atom") + lj("eV/atom") + lj("K", 2) +
        lj("Å^2"))
    file.write(lj("mm^2/s") + lj("GPa"))
    file.write(lj("J/(K*Kg)"))

    if ma:
        file.write(lj("K", 2) + lj("1"))
    file.write("\n")

    file.write(
        lj(" ") + ss(epot_t, d) + ss(ekin_t, d) + ss(etot_t, d) +
        ss(temp_t, 2) + ss(msd_t, d))
    file.write(ss(selfd_t, d) + ss(pr_t, d))
    file.write(ss(Cv, d))
    if ma:
        file.write(ss(debye_t, 2) + ss(linde_t, d))
    file.close()
    return