Source code for thermodynamics

# Copyright 2025-2026 Onera
# This file is part of the Noda package
# SPDX-License-Identifier: GPL-3.0-or-later

"""Handle thermodynamic properties."""

import numpy as np

import noda.thermo_functions as tfu
import noda.constants as co
import noda.utils as ut


[docs] class Thermodynamics: """ Provide methods to compute thermodynamic properties. Methods that compute the Gibbs free energy and chemical potentials as a function of atom fraction array (G_funx and MU_funx) assume vacancies are at equilibrium. Methods that compute the Gibbs free energy and chemical potentials as a function of site fraction array (G_funy and MU_funy) are initialized based on the same assumption, but the class provides a method to update these methods in the case of a non-ideal lattice (set_nonideal_lattice). Attributes ---------- comps : list of str System components. TK : float Temperature in Kelvin. GfV : dict of list Vacancy formation energy in pure metals, given as [enthalpy, entropy] in J/mol and J/mol/K. See :func:`data_io.get_vacancy_formation_energy`. params : dict of floats Thermodynamic parameters arranged as follows: | ``A: G_A for A in endmembers`` | ``AB: [L0, L1] for AB in binary subsystems`` ideal_lattice : bool Whether lattice is ideal, in the sense that the vacancy fraction is maintained at equilibrium. Methods ------- G_funx(x) : Return Gibbs free energy at composition given by atom fraction array. MU_funx(x) : Return chemical potentials at composition given by atom fraction array. yVa_fun(x) : Return equilibrium vacancy fraction at composition given by atom fraction array. G_fun(x) : Alias of G_funx. MU_fun(x): Return MU_funx as a dict. G_funy(y): Return Gibbs free energy at composition given by site fraction array. MU_funy(y) : Return chemical potentials at composition given by site fraction array. """
[docs] def __init__(self, params, comps, phase, TK, GfV, logger): """ Class constructor. Parameters ---------- params : dict of floats Thermodynamic parameters arranged as follows: | ``A: G_A for A in endmembers`` | ``AB: [L0, L1] for AB in binary subsystems`` comps : list of str System components. phase : str Phase name. TK : float Temperature in Kelvin. vacancy_databases : dict Register of vacancy formation energy databases. vacancy_db : str Name of database with vacancy formation energy in pure metals. logger : :class:`log_utils.CustomLogger` Logger. """ self.comps = comps self.TK = TK self.GfV = GfV self.params = self.process_parameters(params) self.G_funx = make_G_fun(comps[1:], self.params, TK) self.MU_funx = make_MU_fun(comps[1:], self.params, TK) self.yVa_fun = make_yVa_fun(comps, self.params, TK) self.G_fun = self.G_funx self.ideal_lattice = True self.G_funy = self.extend_G_funx() self.MU_funy = self.extend_MU_funx()
[docs] def process_parameters(self, params): """Add vacancy-related interactions to thermodynamic parameters.""" params['Va'] = co.GV0 for k in self.comps[1:]: solvent = 'Va' + k p0 = tfu.LkV_fun(self.GfV, k, self.TK) params[solvent] = [p0, 0] for i in range(1, len(self.comps) - 1): for j in range(i + 1, len(self.comps)): A = self.comps[i] B = self.comps[j] params['Va' + A + B] = [0, 0] return params
[docs] def extend_G_funx(self): """ Make function that computes G from site fractions. Here the vacancy site fraction is at its equilibrium value. """ def fun(y): x = y[1:]/(1 - y[0]) return self.G_funx(x) return fun
[docs] def extend_MU_funx(self): """ Make function that computes MU from site fractions. Here the vacancy site fraction is at its equilibrium value (MU_Va = 0). """ def fun(y): x = y[1:]/(1 - y[0]) MU = self.MU_funx(x) res = np.vstack((np.zeros(MU.shape[1]), MU)) return res return fun
[docs] def set_nonideal_lattice(self): """ Update G_funy and MU_funy functions in cas of non-ideal lattice. Update functions that compute Gibbs free energy and chemical potentials from site fraction array when lattice is non-ideal (vacancy site fraction not maintained at equilibrium). """ self.ideal_lattice = False self.G_funy = make_G_fun(self.comps, self.params, self.TK) self.MU_funy = make_MU_fun(self.comps, self.params, self.TK)
[docs] def MU_fun(self, x): """ Calculate chemical potential of atom components. Parameters ---------- x : np.array (shape (`n_inds`, `nz`)) Atom fractions. Returns ------- dict Chemical potentials, shape (`nz`) ``{k: mu_k for k in components}``. """ return {k: self.MU_funx(x)[i] for i, k in enumerate(self.comps[1:])}
[docs] def make_G_fun(comps, pdict, TK): """ Make function to compute Gibbs free energy. Process parameters and call :func:`thermo_functions.G_model`. Parameters ---------- comps : list of str System constituents. pdict : dict Thermodynamic parameters. TK : float Temperature in Kelvin. Returns ------- fun : function Function that takes composition array (shape (`n_inds`, `nz`)) as argument, and returns Gibbs free energy evaluated on this composition grid (shape (`nz`,)). """ solvents = ut.make_combinations(comps) G0 = [pdict[k] for k in comps] L = [] for s in solvents['mix']: L.append(pdict[s][0]) L.append(pdict[s][1]) L = np.array(L) def fun(x): return tfu.G_model(x, G0, L, TK) return fun
[docs] def make_MU_fun(comps, pdict, TK): """ Make function that computes chemical potentials. Process parameters and call :func:`thermo_functions.MU_model`. Use a Redlich-Kister polynomial with binary interactions of order 0 and 1 (see :func:`thermo_functions.MU_model`). Parameters ---------- comps : list of str System constituents. pdict : dict of floats Thermodynamic parameters. TK : float Temperature in Kelvin. Returns ------- fun : function Function that takes composition array (shape (`n_inds`, `nz`)) as argument, and returns chemical potentials evaluated on this composition grid (shape (`n_comps`, `nz`)). """ solvents = ut.make_combinations(comps) parr = [pdict[k] for k in comps] for s in solvents['mix']: parr.append(pdict[s][0]) parr.append(pdict[s][1]) def fun(x): return tfu.MU_model(x, parr, TK) return fun
[docs] def make_yVa_fun(comps, pdict, TK): """ Make function to compute equilibrium vacancy site fractions. Process parameters and call :func:`thermo_functions.yVa_model`. Parameters ---------- comps : list of str System constituents, including Va. pdict : dict Thermodynamic parameters. TK : float Temperature in Kelvin. Returns ------- fun : function Function that takes atom fractions (shape (`n_inds`, `nz`)) as argument, and returns the equilibrium vacancy fraction evaluated on this composition array (shape (`nz`,)). """ solvents = ut.make_combinations(comps) G0 = [pdict[k] for k in solvents['unaries']] L = np.array([pdict[k] for k in solvents['mix']]) def fun(x): return tfu.yVa_model(x, G0, L, TK) return fun