def calculatePartialMolarVolume(self,elementMolarAmounts,endmemberMassDensityLaws, epsilon, constituentToEndmembersConverter=None):
     #print(elementMolarAmounts, epsilon)
     # suspend all other phases
     oc.setPhasesStatus(('* ',), phStat.Suspended)
     oc.setPhasesStatus(tuple(self.__phasenames), phStat.Entered, 1.0)
     # set pressure
     oc.setPressure(self.__P)
     # set temperature
     oc.setTemperature(self.__T)
     # evaluate volume
     oc.setElementMolarAmounts(elementMolarAmounts)
     oc.calculateEquilibrium(gmStat.Off)
     exactVolume = self.__calculateVolume(oc.getPhasesAtEquilibrium().getPhaseConstituentComposition(),oc.getConstituentsDescription(),endmemberMassDensityLaws, constituentToEndmembersConverter)
     # evaluate (elementwise) partial molar volume (approximation of first order volume derivative by a second-order finite difference formula)
     partialMolarVolumes={}
     for element in elementMolarAmounts:
         # evaluate volume for n[element]+epsilone
         modifiedElementMolarAmounts=elementMolarAmounts.copy()
         modifiedElementMolarAmounts[element] += epsilon
         #print(element, modifiedElementMolarAmounts)
         oc.setElementMolarAmounts(modifiedElementMolarAmounts)
         oc.calculateEquilibrium(gmStat.Off)
         volumePlus = self.__calculateVolume(oc.getPhasesAtEquilibrium().getPhaseConstituentComposition(),oc.getConstituentsDescription(),endmemberMassDensityLaws, constituentToEndmembersConverter)
         # evaluate volume for n[element]-epsilone
         modifiedElementMolarAmounts[element] -= 2.0*epsilon
         #print(element, modifiedElementMolarAmounts)
         oc.setElementMolarAmounts(modifiedElementMolarAmounts)
         oc.calculateEquilibrium(gmStat.Off)
         volumeMinus = self.__calculateVolume(oc.getPhasesAtEquilibrium().getPhaseConstituentComposition(),oc.getConstituentsDescription(),endmemberMassDensityLaws, constituentToEndmembersConverter)
         partialMolarVolumes[element]=(volumePlus-volumeMinus)/(2.0*epsilon)
     # calculate approximate volume from partial volumes
     approxVolume = 0.0
     for element, molarAmount in oc.getPhasesAtEquilibrium().getPhaseElementComposition()[list(oc.getPhasesAtEquilibrium().getPhaseElementComposition())[0]].items():
         approxVolume+=molarAmount*partialMolarVolumes[element]
     return partialMolarVolumes,exactVolume,approxVolume
    def calculateEquilibria(self, gmStat, setverb, T, P, x0):

        # setting verbosity (True or False - default), if set to yes, in particular, when getters are called the returned values are displayed in a comprehensive way
        oc.setVerbosity(setverb)

        # set pressure
        oc.setPressure(P)

        # set temperature
        oc.setTemperature(T)

        # set initial molar amounts
        oc.setElementMolarAmounts(x0)

        #Equilibrium
        if gmStat == 'Off':
            oc.calculateEquilibrium(gmstat.Off)
        elif gmStat == 'On':
            oc.calculateEquilibrium(gmstat.On)
        else:
            raise ValueError(
                'No suitable parameter for gmstat found: Choose from On/Off')

        self.gibbs = oc.getGibbsEnergy()
        self.mu = oc.getChemicalPotentials()
        self.cd = oc.getConstituentsDescription()
        self.mass = oc.getScalarResult('B')

        phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
        self.pec = phasesAtEquilibrium.getPhaseElementComposition()
        self.pcc = phasesAtEquilibrium.getPhaseConstituentComposition()
        self.ps = phasesAtEquilibrium.getPhaseSites()
        self.ma = phasesAtEquilibrium.getPhaseMolarAmounts()
Ejemplo n.º 3
0
    def eqfunc(self, x, calc_value):

        # setting verbosity (True or False - default), if set to yes, in particular, when getters are called the returned values are displayed in a comprehensive way
        oc.setVerbosity(self.setverb)

        # set pressure
        oc.setPressure(self.P)

        # set temperature
        oc.setTemperature(self.T)

        # set initial molar amounts
        oc.setElementMolarAmounts(x)

        #Equilibrium
        oc.calculateEquilibrium(gmstat.Off)

        if calc_value == 'gibbs':
            self.eq_val = oc.getGibbsEnergy()

        elif calc_value == 'mu':
            self.eq_val = oc.getChemicalPotentials()

        elif calc_value == 'cd':
            self.eq_val = oc.getConstituentsDescription()

        elif calc_value == 'mass':
            self.eq_val = oc.getScalarResult('B')

        elif calc_value == 'pec':
            self.eq_val = oc.getPhasesAtEquilibrium(
            ).getPhaseElementComposition()

        elif calc_value == 'pcc':
            self.eq_val = oc.getPhasesAtEquilibrium(
            ).getPhaseConstituentComposition()

        elif calc_value == 'ps':
            self.eq_val = oc.getPhasesAtEquilibrium().getPhaseSites()

        elif calc_value == 'ma':
            self.eq_val = oc.getPhasesAtEquilibrium().getPhaseMolarAmounts()

        return self.eq_val
def Gm_bulkphase(temperature, elementMolarAmounts):
    # set temperature
    oc.setTemperature(temperature)
    oc.setElementMolarAmounts(elementMolarAmounts)
    Gm_bulkphase = {}
    Gm_bulkphase = oc.calculateEquilibrium(gmStat.On)
    G = oc.getScalarResult('G')
    phase_description = oc.getPhasesAtEquilibrium(
    ).getPhaseConstituentComposition()
    chemicalpotential = oc.getComponentAssociatedResult('MU')
 def phase(self, x, **kwargs):
     """
     The string name of the phase in equilibrium at the conditions.
     """
     phasearray = self.eqfunc(x).Phase.sel(P=self.P, T=self.T, **kwargs)
     return phasearray.values.flatten()
     
     phasesAtEquilibrium=oc.getPhasesAtEquilibrium()
     
     
     self.pec = phasesAtEquilibrium.getPhaseElementComposition()
     self.pcc = phasesAtEquilibrium.getPhaseConstituentComposition() 
     self.ps = phasesAtEquilibrium.getPhaseSites()
     self.ma = phasesAtEquilibrium.getPhaseMolarAmounts()        
     self.mu = oc.getChemicalPotentials()
     self.cd = oc.getConstituentsDescription()
     self.mass=oc.getScalarResult('B')
def run():
    print(
        '### test U-O coherent interface in the liquid miscibility gap ###\n')
    # tdb filepath
    tdbFile = os.environ['TDBDATA_PRIVATE'] + '/feouzr.tdb'
    #tdbFile=os.environ['TDBDATA_PRIVATE']+'/NUCLEA-17_1_mod.TDB'
    #tdbFile=os.environ['TDBDATA_PRIVATE']+'/NUCLEA-19_1_mod.TDB'
    #tdbFile='tests/TAF_uzrofe_V10.TDB'
    #tdbFile='tests/TAF_uzrofe_V10_only_liquid_UO.TDB'
    # components
    comps = ['O', 'U']
    # mass density laws (from Barrachin2004)
    constituentDensityLaws = {
        'U1':
        lambda T: 17270.0 - 1.358 * (T - 1408),
        'ZR1':
        lambda T: 6844.51 - 0.609898 * T + 2.05008E-4 * T**2 - 4.47829E-8 * T**
        3 + 3.26469E-12 * T**4,
        'O2U1':
        lambda T: 8860.0 - 9.285E-1 * (T - 3120),
        'O2ZR1':
        lambda T: 5150 - 0.445 * (T - 2983),
        'O1':
        lambda T:
        1.141  # set to meaningless value but ok as, no 'free' oxygen in the considered mixtures
    }
    constituentDensityLaws['U'] = constituentDensityLaws['U1']
    constituentDensityLaws['ZR'] = constituentDensityLaws['ZR1']
    constituentDensityLaws['O'] = constituentDensityLaws['O1']

    # phase names
    phasenames = ['LIQUID', 'LIQUID']
    # pressure
    P = 1E5
    # Given initial alloy composition. x0 is the mole fraction of U.
    x0 = [0.65]
    # Composition step for searching initial interfacial equilibrium composition.
    dx = 0.05
    # Convergence criterion for loop on interfacial composition
    epsilonX = 1E-5

    # temperature range
    Tmin = 2800.0
    Tmax = 4400.0
    Trange = np.linspace(Tmin, Tmax, num=60, endpoint=True)
    #Tmin = 2800.0
    #Tmax = 2900.0
    #Trange = np.linspace(Tmin, Tmax, num=3, endpoint=True)
    results = pd.DataFrame(columns=[
        'temperature', 'n_phase1', 'n_phase2', 'xU_phase1', 'xU_phase2',
        'xU_interface', 'sigma', 'VmU', 'VmO'
    ])

    for T in Trange:
        # calculate global equilibrium and retrieve associated chemical potentials
        CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
        model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
        mueq = model.chemicalpotential(x0)
        phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
        phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
        )
        if (len(phasesAtEquilibriumMolarAmounts) == 1):
            # it is possible that the miscibility gap has not been detected correctly (can happen when T increases)
            #print(phasesAtEquilibriumMolarAmounts)
            # ad hoc strategy: 1) calculate an equilibrium at lower temperature (hopefully finding the two phases)
            #                  2) redo the calculation at the target temperature afterwards without the grid minimizer
            model = CoherentGibbsEnergy_OC(T - 300.0, 1E5, phasenames)
            mueq = model.chemicalpotential(x0)
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
            #print(phasesAtEquilibriumMolarAmounts)
            oc.setTemperature(T)
            oc.calculateEquilibrium(gmStat.Off)
            mueq = model.getChemicalPotentials()
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
        phasesAtEquilibriumElementCompositions = phasesAtEquilibrium.getPhaseElementComposition(
        )
        print(phasesAtEquilibriumMolarAmounts)
        if (set(phasesAtEquilibriumMolarAmounts) == set(
            ['LIQUID#1', 'LIQUID_AUTO#2'])):
            # Composition range for searching initial interfacial equilibrium composition
            # calculated from the actual phase compositions
            componentsWithLimits = comps[1:]
            limit = [[1.0, 0.0] for each in componentsWithLimits]
            for phase in phasesAtEquilibriumElementCompositions:
                for element in phasesAtEquilibriumElementCompositions[phase]:
                    elementMolarFraction = phasesAtEquilibriumElementCompositions[
                        phase][element]
                    if element in componentsWithLimits:
                        limit[componentsWithLimits.index(element)][0] = min(
                            limit[componentsWithLimits.index(element)][0],
                            elementMolarFraction)
                        limit[componentsWithLimits.index(element)][1] = max(
                            limit[componentsWithLimits.index(element)][1],
                            elementMolarFraction)
            limit = [[
                each[0] + dx * (each[1] - each[0]),
                each[1] - dx * (each[1] - each[0])
            ] for each in limit]
            print('limits: ', limit)

            notConverged = True
            x = x0.copy()
            # Iterate on interfacial molar composition
            while (notConverged):
                # Molar volumes of pure components evaluated at x
                CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
                model = CoherentGibbsEnergy_OC(T, P, phasenames[0], False)
                if ('TAF' in tdbFile):
                    functions = model.constantPartialMolarVolumeFunctions(
                        x, constituentDensityLaws, 1E-5,
                        constituentToEndmembersConverter)
                else:
                    functions = model.constantPartialMolarVolumeFunctions(
                        x, constituentDensityLaws, 1E-5)
                # calculate interfacial energy
                sigma = SigmaCoherent_OC(T=T,
                                         x0=x0,
                                         db=tdbFile,
                                         comps=comps,
                                         phasenames=phasenames,
                                         purevms=functions,
                                         limit=limit,
                                         dx=dx,
                                         enforceGridMinimizerForLocalEq=False,
                                         mueq=mueq)
                print('at T=', T, ' sigma=', sigma.Interfacial_Energy.values,
                      '\n')
                notConverged = np.abs(
                    x[0] - sigma.Interfacial_Composition.values[1]) > epsilonX
                print('convergence: ', not notConverged, x[0],
                      sigma.Interfacial_Composition.values[1])
                x[0] = sigma.Interfacial_Composition.values[1]
            # Store result
            if (np.abs(sigma.Interfacial_Energy.values) > 1E-6):
                # store results in pandas dataframe
                results = results.append(
                    {
                        'temperature':
                        T,
                        'n_phase1':
                        phasesAtEquilibriumMolarAmounts['LIQUID#1'],
                        'n_phase2':
                        phasesAtEquilibriumMolarAmounts['LIQUID_AUTO#2'],
                        'xU_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['U'],
                        'xU_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['U'],
                        'xU_interface':
                        sigma.Interfacial_Composition.values[1],
                        'sigma':
                        sigma.Interfacial_Energy.values,
                        'VmU':
                        functions[1](T),
                        'VmO':
                        functions[0](T),
                    },
                    ignore_index=True)
            else:
                raise ValueError('wrong value discarded')
        else:
            print('at T=', T, ' out of the miscibility gap')
        print('phases at equilibrium:', phasesAtEquilibriumMolarAmounts)
    # write csv result file
    results.to_csv('macro_liquidMG_UO_run.csv')
def run3(tdbFile, RUZr):
    print(
        '### test U-O-Zr-Fe coherent interface in the liquid miscibility gap ###\n'
    )
    # components
    comps = ['O', 'U', 'ZR', 'FE']
    # mass density laws (from Barrachin2004)
    constituentDensityLaws = {
        'U1':
        lambda T: 17270.0 - 1.358 * (T - 1408),
        'ZR1':
        lambda T: 6844.51 - 0.609898 * T + 2.05008E-4 * T**2 - 4.47829E-8 * T**
        3 + 3.26469E-12 * T**4,
        'O2U1':
        lambda T: 8860.0 - 9.285E-1 * (T - 3120),
        'O2ZR1':
        lambda T: 5150 - 0.445 * (T - 2983),
        'FE1':
        lambda T: 7030 - 0.88 * (T - 1808),
        'NI1':
        lambda T: 7900 - 1.19 * (T - 1728),
        'CR1':
        lambda T: 6290 - 0.72 * (T - 2178),
        'O1':
        lambda T:
        1.141,  # set to meaningless value but ok as, no 'free' oxygen in the considered mixtures
        'FE1O1':
        lambda T: 7030 - 0.88 * (
            T - 1808
        ),  # set to Fe value but ok as, almost no such component in the considered mixtures
        'FE1O1_5':
        lambda T: 7030 - 0.88 * (
            T - 1808
        ),  # set to Fe value but ok as, almost no such component in the considered mixtures
    }
    # phase names
    phasenames = ['LIQUID', 'LIQUID']
    # pressure
    P = 1E5
    # initial alloy compositions. x0 is the mole fractions of U, Zr, Fe.
    read = pd.read_csv('tests/{0:2.1f}RUZr.csv'.format(RUZr),
                       delim_whitespace=True)
    # Composition step for searching initial interfacial equilibrium composition.
    #dx = 0.5
    # Convergence criterion for loop on interfacial composition
    epsilonX = 1E-4

    # temperature range
    T = 3000
    # Trange = np.linspace(Tmin, Tmax, num=10, endpoint=True)
    results = pd.DataFrame(columns=[
        'temperature', 'n_phase1', 'n_phase2', 'xU_phase1', 'xU_phase2',
        'xZr_phase1', 'xZr_phase2', 'xFe_phase1', 'xFe_phase2', 'xU_interface',
        'xZr_interface', 'xFe_interface', 'sigma', 'VmU', 'VmZr', 'VmFe'
    ])

    x = None
    for ii in range(read.shape[0]):
        x0 = [read['xU'][ii], read['xZr'][ii], read['xFe'][ii]]
        print("*********({0:d}/{1:d})*********".format(ii + 1, read.shape[0]))
        print("x0: ", x0)
        # calculate global equilibrium and retrieve associated chemical potentials
        CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
        oc.raw().pytqtgsw(4)  # no merging of grid points
        #oc.raw().pytqtgsw(23) # denser grid
        model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
        mueq = model.chemicalpotential(x0)
        phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
        phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
        )
        if (len(phasesAtEquilibriumMolarAmounts) == 1):
            # it is possible that the miscibility gap has not been detected correctly (can happen when T increases)
            #print(phasesAtEquilibriumMolarAmounts)
            # ad hoc strategy: 1) calculate an equilibrium at lower temperature (hopefully finding the two phases)
            #                  2) redo the calculation at the target temperature afterwards without the grid minimizer
            model = CoherentGibbsEnergy_OC(2900, 1E5, phasenames)
            mueq = model.chemicalpotential(x0)
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
            #print(phasesAtEquilibriumMolarAmounts)
            oc.setTemperature(T)
            oc.calculateEquilibrium(gmStat.Off)
            mueq = model.getChemicalPotentials()
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
        phasesAtEquilibriumElementCompositions = phasesAtEquilibrium.getPhaseElementComposition(
        )
        print(phasesAtEquilibriumMolarAmounts)
        if (set(phasesAtEquilibriumMolarAmounts) == set(
            ['LIQUID#1', 'LIQUID_AUTO#2'])):
            # Composition range for searching initial interfacial equilibrium composition
            # calculated from the actual phase compositions
            componentsWithLimits = comps[1:]
            #limit = [ [1.0, 0.0] for each in componentsWithLimits ]
            #for phase in phasesAtEquilibriumElementCompositions:
            #for element in phasesAtEquilibriumElementCompositions[phase]:
            #        elementMolarFraction = phasesAtEquilibriumElementCompositions[phase][element]
            #        if element in componentsWithLimits:
            #            limit[componentsWithLimits.index(element)][0] = min(limit[componentsWithLimits.index(element)][0], elementMolarFraction)
            #            limit[componentsWithLimits.index(element)][1] = max(limit[componentsWithLimits.index(element)][1], elementMolarFraction)
            #limit = [ [each[0]+dx*(each[1]-each[0]), each[1]-dx*(each[1]-each[0])] for each in limit ]
            bulkX = [[
                phasesAtEquilibriumElementCompositions[phase][element]
                for phase in phasesAtEquilibriumMolarAmounts
            ] for element in componentsWithLimits]

            notConverged = True
            if (x == None):
                x = [
                    0.5 *
                    (phasesAtEquilibriumElementCompositions['LIQUID#1'][comp] +
                     phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                     [comp]) for comp in componentsWithLimits
                ]
            # Iterate on interfacial molar composition
            while (notConverged):
                # Molar volumes of pure components evaluated at x
                CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
                model = CoherentGibbsEnergy_OC(T, P, phasenames[0], False)
                if ('TAF' in tdbFile):
                    functions = model.constantPartialMolarVolumeFunctions(
                        x, constituentDensityLaws, 1E-5,
                        constituentToEndmembersConverter)
                else:
                    functions = model.constantPartialMolarVolumeFunctions(
                        x, constituentDensityLaws, 1E-5)
                # calculate interfacial energy
                sigma = SigmaCoherent_OC2(
                    T=T,
                    x0=x0,
                    db=tdbFile,
                    comps=comps,
                    phasenames=phasenames,
                    purevms=functions,
                    guess=x,
                    computeEquilibriumFunction=partial(
                        ComputeEquilibriumWithConstraints, bulkX=bulkX),
                    enforceGridMinimizerForLocalEq=False,
                    mueq=mueq)
                print('at T=', T, ' sigma=', sigma.Interfacial_Energy.values,
                      '\n')
                notConverged = np.linalg.norm(
                    x[:] - sigma.Interfacial_Composition.values[1:],
                    np.inf) > epsilonX
                print('convergence: ', not notConverged, x[:],
                      sigma.Interfacial_Composition.values[1:])
                x[:] = sigma.Interfacial_Composition.values[1:]
            # store results in pandas dataframe
            if (np.abs(sigma.Interfacial_Energy.values) > 1E-5):
                print(sigma, "\n")
                if (abs(
                        np.max(sigma.Partial_Interfacial_Energy.values) -
                        np.min(sigma.Partial_Interfacial_Energy.values)) >
                        1E-4):
                    print(np.min(sigma.Partial_Interfacial_Energy.values))
                    print(np.max(sigma.Partial_Interfacial_Energy.values))
                    raise ValueError('wrong value discarded')
                results = results.append(
                    {
                        'temperature':
                        T,
                        'n_phase1':
                        phasesAtEquilibriumMolarAmounts['LIQUID#1'],
                        'n_phase2':
                        phasesAtEquilibriumMolarAmounts['LIQUID_AUTO#2'],
                        'xU_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['U'],
                        'xU_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['U'],
                        'xZr_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['ZR'],
                        'xZr_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['ZR'],
                        'xFe_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['FE'],
                        'xFe_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['FE'],
                        'xU_interface':
                        sigma.Interfacial_Composition.values[1],
                        'xZr_interface':
                        sigma.Interfacial_Composition.values[2],
                        'xFe_interface':
                        sigma.Interfacial_Composition.values[3],
                        'sigma':
                        sigma.Interfacial_Energy.values,
                        'VmU':
                        functions[1](T),
                        'VmZr':
                        functions[2](T),
                        'VmFe':
                        functions[3](T),
                        'VmO':
                        functions[0](T),
                    },
                    ignore_index=True)
            else:
                raise ValueError('wrong value discarded')
        else:
            print('at T=', T, ' out of the miscibility gap')
        print('phases at equilibrium:', phasesAtEquilibriumMolarAmounts)
    # write csv result file
    if ('TAF' in tdbFile):
        results.to_csv(
            'macro_liquidMG_UOZrFe_run3_TAFID_RUZR={0:2.1f}.csv'.format(RUZr))
    else:
        results.to_csv(
            'macro_liquidMG_UOZrFe_run3_RUZR={0:2.1f}.csv'.format(RUZr))
def run2():
    print(
        '### test U-O coherent interface in the liquid miscibility gap ###\n')
    # tdb filepath
    #tdbFile=os.environ['TDBDATA_PRIVATE']+'/feouzr.tdb'
    #tdbFile=os.environ['TDBDATA_PRIVATE']+'/NUCLEA-17_1_mod.TDB'
    #tdbFile=os.environ['TDBDATA_PRIVATE']+'/NUCLEA-19_1_mod.TDB'
    tdbFile = 'tests/TAF_uzrofe_V10.TDB'
    # components
    comps = ['O', 'U', 'ZR', 'FE']
    # mass density laws (from Barrachin2004)
    constituentDensityLaws = {
        'U1':
        lambda T: 17270.0 - 1.358 * (T - 1408),
        'ZR1':
        lambda T: 6844.51 - 0.609898 * T + 2.05008E-4 * T**2 - 4.47829E-8 * T**
        3 + 3.26469E-12 * T**4,
        'O2U1':
        lambda T: 8860.0 - 9.285E-1 * (T - 3120),
        'O2ZR1':
        lambda T: 5150 - 0.445 * (T - 2983),
        'FE1':
        lambda T: 7030 - 0.88 * (T - 1808),
        'NI1':
        lambda T: 7900 - 1.19 * (T - 1728),
        'CR1':
        lambda T: 6290 - 0.72 * (T - 2178),
        'O1':
        lambda T:
        1.141,  # set to meaningless value but ok as, no 'free' oxygen in the considered mixtures
        'FE1O1':
        lambda T: 7030 - 0.88 * (
            T - 1808
        ),  # set to Fe value but ok as, almost no such component in the considered mixtures
        'FE1O1_5':
        lambda T: 7030 - 0.88 * (
            T - 1808
        ),  # set to Fe value but ok as, almost no such component in the considered mixtures
    }

    constituentDensityLaws['U'] = constituentDensityLaws['U1']
    constituentDensityLaws['ZR'] = constituentDensityLaws['ZR1']
    constituentDensityLaws['O'] = constituentDensityLaws['O1']
    constituentDensityLaws['FE'] = constituentDensityLaws['FE1']

    # phase names
    phasenames = ['LIQUID', 'LIQUID']
    # pressure
    P = 1E5
    # Given initial alloy composition. x0 is the mole fractions of U, Zr, Fe.
    # RU/Zr=0.60 CZr=0.3 xSteel=0.1
    x0 = [0.1550142, 0.2583569, 0.1215864]
    # Composition step for searching initial interfacial equilibrium composition.
    #dx = 0.5
    # Convergence criterion for loop on interfacial composition
    epsilonX = 1E-5

    inputs = pd.read_csv('macro_liquidMG_UOZrFe_run.csv')
    results = pd.DataFrame(columns=[
        'temperature', 'n_phase1', 'n_phase2', 'xU_phase1', 'xU_phase2',
        'xZr_phase1', 'xZr_phase2', 'xFe_phase1', 'xFe_phase2', 'xU_interface',
        'xZr_interface', 'xFe_interface', 'VmU', 'VmZr', 'VmFe', 'sigma'
    ])

    x = None
    for i, T in enumerate(inputs['temperature']):
        # calculate global equilibrium and retrieve associated chemical potentials
        CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
        oc.raw().pytqtgsw(4)  # no merging of grid points
        #oc.raw().pytqtgsw(23) # denser grid
        model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
        mueq = model.chemicalpotential(x0)
        phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
        phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
        )
        if (len(phasesAtEquilibriumMolarAmounts) == 1):
            # it is possible that the miscibility gap has not been detected correctly (can happen when T increases)
            #print(phasesAtEquilibriumMolarAmounts)
            # ad hoc strategy: 1) calculate an equilibrium at lower temperature (hopefully finding the two phases)
            #                  2) redo the calculation at the target temperature afterwards without the grid minimizer
            model = CoherentGibbsEnergy_OC(2800.0, 1E5, phasenames)
            mueq = model.chemicalpotential(x0)
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
            #print(phasesAtEquilibriumMolarAmounts)
            oc.setTemperature(T)
            oc.calculateEquilibrium(gmStat.Off)
            mueq = model.getChemicalPotentials()
            phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
            phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
            )
        phasesAtEquilibriumElementCompositions = phasesAtEquilibrium.getPhaseElementComposition(
        )
        print(phasesAtEquilibriumMolarAmounts)
        print(phasesAtEquilibriumElementCompositions)
        if (set(phasesAtEquilibriumMolarAmounts) == set(
            ['LIQUID#1', 'LIQUID_AUTO#2'])):
            # Composition range for searching initial interfacial equilibrium composition
            # calculated from the actual phase compositions
            componentsWithLimits = comps[1:]
            #limit = [ [1.0, 0.0] for each in componentsWithLimits ]
            #for phase in phasesAtEquilibriumElementCompositions:
            #    for element in phasesAtEquilibriumElementCompositions[phase]:
            #        elementMolarFraction = phasesAtEquilibriumElementCompositions[phase][element]
            #        if element in componentsWithLimits:
            #            limit[componentsWithLimits.index(element)][0] = min(limit[componentsWithLimits.index(element)][0], elementMolarFraction)
            #            limit[componentsWithLimits.index(element)][1] = max(limit[componentsWithLimits.index(element)][1], elementMolarFraction)
            #limit = [ [each[0]+dx*(each[1]-each[0]), each[1]-dx*(each[1]-each[0])] for each in limit ]
            bulkX = [[
                phasesAtEquilibriumElementCompositions[phase][element]
                for phase in phasesAtEquilibriumMolarAmounts
            ] for element in componentsWithLimits]

            if (x == None):
                x = [
                    0.5 *
                    (phasesAtEquilibriumElementCompositions['LIQUID#1'][comp] +
                     phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                     [comp]) for comp in componentsWithLimits
                ]
            #x = x0.copy()
            # Molar volumes of pure components evaluated at x
            functions = [
                lambda _: inputs['VmO'][i], lambda _: inputs['VmU'][i],
                lambda _: inputs['VmZr'][i], lambda _: inputs['VmFe'][i]
            ]
            # calculate interfacial energy
            sigma = SigmaCoherent_OC2(T=T,
                                      x0=x0,
                                      db=tdbFile,
                                      comps=comps,
                                      phasenames=phasenames,
                                      purevms=functions,
                                      guess=x,
                                      computeEquilibriumFunction=partial(
                                          ComputeEquilibriumWithConstraints,
                                          bulkX=bulkX),
                                      enforceGridMinimizerForLocalEq=False,
                                      mueq=mueq)
            print('at T=', T, ' sigma=', sigma.Interfacial_Energy.values, '\n')
            x[:] = sigma.Interfacial_Composition.values[1:]
            # Store result
            if (np.abs(sigma.Interfacial_Energy.values) > 1E-6):
                # store results in pandas dataframe
                print(sigma, "\n")
                results = results.append(
                    {
                        'temperature':
                        T,
                        'n_phase1':
                        phasesAtEquilibriumMolarAmounts['LIQUID#1'],
                        'n_phase2':
                        phasesAtEquilibriumMolarAmounts['LIQUID_AUTO#2'],
                        'xU_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['U'],
                        'xU_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['U'],
                        'xZr_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['ZR'],
                        'xZr_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['ZR'],
                        'xFe_phase1':
                        phasesAtEquilibriumElementCompositions['LIQUID#1']
                        ['FE'],
                        'xFe_phase2':
                        phasesAtEquilibriumElementCompositions['LIQUID_AUTO#2']
                        ['FE'],
                        'xU_interface':
                        sigma.Interfacial_Composition.values[1],
                        'xZr_interface':
                        sigma.Interfacial_Composition.values[2],
                        'xFe_interface':
                        sigma.Interfacial_Composition.values[3],
                        'sigma':
                        sigma.Interfacial_Energy.values,
                        'VmU':
                        functions[0](T),
                        'VmZr':
                        functions[1](T),
                        'VmFe':
                        functions[2](T),
                    },
                    ignore_index=True)
            else:
                print(sigma, "\n")
                raise ValueError('wrong value discarded')
        else:
            print('at T=', T, ' out of the miscibility gap')
        print('phases at equilibrium:', phasesAtEquilibriumMolarAmounts)
    # write csv result file
    results.to_csv('macro_liquidMG_UOZrFe_run2.csv')
oc.setPressure(1E5)

# set temperature
oc.setTemperature(2800)
## suspend all phases except the liquid one
oc.setPhasesStatus(('* ', ), phStat.Suspended)
oc.setPhasesStatus(('LIQUID', ), phStat.Entered)

oc.setElementMolarAmounts(x0)

# calculate equilibrium
oc.changeEquilibriumRecord('eq2')
oc.calculateEquilibrium(gmStat.Off)

# retrieving mu data
phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
phaseConstituentComposition = phasesAtEquilibrium.getPhaseConstituentComposition(
)
mueq_inf = oc.getChemicalPotentials()

# calculate equilibrium with the grid-minimizer (equilibrium record = default equilibrium)
oc.changeEquilibriumRecord()
oc.calculateEquilibrium()

# retrieving mu data
phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
phaseConstituentComposition = phasesAtEquilibrium.getPhaseConstituentComposition(
)
mueq = oc.getChemicalPotentials()

# Use of molarVolume class
def run_TAFID():
    print(
        '### test U-O coherent interface in the liquid miscibility gap ###\n')
    # tdb filepath
    tdbFile = os.environ['TDBDATA_PRIVATE'] + '/feouzr.tdb'
    # tdbFile=os.environ['TDBDATA_PRIVATE']+'/NUCLEA-17_1_mod.TDB'
    #    tdbFile='tests/TAF_uzrofe_V10.TDB'
    # components
    comps = ['O', 'U']
    # mass density laws (from Barrachin2004)
    constituentDensityLaws = {
        'U1':
        lambda T: 17270.0 - 1.358 * (T - 1408),
        'ZR1':
        lambda T: 6844.51 - 0.609898 * T + 2.05008E-4 * T**2 - 4.47829E-8 * T**
        3 + 3.26469E-12 * T**4,
        'O2U1':
        lambda T: 8860.0 - 9.285E-1 * (T - 3120),
        'O2ZR1':
        lambda T: 5150 - 0.445 * (T - 2983),
        'O1':
        lambda T:
        1.141  # set to meaningless value but ok as, no 'free' oxygen in the considered mixtures
    }
    constituentDensityLaws['U'] = constituentDensityLaws['U1']
    constituentDensityLaws['ZR'] = constituentDensityLaws['ZR1']
    constituentDensityLaws['O'] = constituentDensityLaws['O1']
    # phase names
    phasenames = ['LIQUID#1', 'LIQUID#2']
    # pressure & temp
    P = 1E5
    T = 3200
    # Given initial alloy composition. x0 is the mole fraction of U.
    x0min = [0.5]
    x0max = [0.7]
    x0range = np.linspace(x0min[0], x0max[0], num=20, endpoint=True)

    # Composition step for searching initial interfacial equilibrium composition.
    dx = 0.05
    results_1 = pd.DataFrame(
        columns=['X_U', 'n_phase1', 'n_phase2', 'mu_U', 'mu_O'])

    for x0 in x0range:
        # Molar volumes of pure components evaluated at x0 and kept constant afterwards
        CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
        model = CoherentGibbsEnergy_OC(T, P, phasenames[0], False)
        # # functions=model.constantPartialMolarVolumeFunctions(x0, constituentDensityLaws, 1E-5, constituentToEndmembersConverter)
        functions = model.constantPartialMolarVolumeFunctions(
            [x0], constituentDensityLaws, 1E-5)

        # calculate global equilibrium and retrieve associated chemical potentials
        model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
        mueq = model.chemicalpotential([x0])
        print('mu_U= ', mueq[1])
        phasesAtEquilibrium = oc.getPhasesAtEquilibrium()
        phasesAtEquilibriumMolarAmounts = phasesAtEquilibrium.getPhaseMolarAmounts(
        )

        phasesAtEquilibriumElementCompositions = phasesAtEquilibrium.getPhaseElementComposition(
        )
        print(phasesAtEquilibriumMolarAmounts)
        if (set(phasesAtEquilibriumMolarAmounts) == set(
            ['LIQUID#1', 'LIQUID_AUTO#2'])):
            # Composition range for searching initial interfacial equilibrium composition
            # calculated from the actual phase compositions
            componentsWithLimits = comps[1:]
            limit = [[1.0, 0.0] for each in componentsWithLimits]
            for phase in phasesAtEquilibriumElementCompositions:
                for element in phasesAtEquilibriumElementCompositions[phase]:
                    elementMolarFraction = phasesAtEquilibriumElementCompositions[
                        phase][element]
                    if element in componentsWithLimits:
                        limit[componentsWithLimits.index(element)][0] = min(
                            limit[componentsWithLimits.index(element)][0],
                            elementMolarFraction)
                        limit[componentsWithLimits.index(element)][1] = max(
                            limit[componentsWithLimits.index(element)][1],
                            elementMolarFraction)
            limit = [[each[0] + dx, each[1] - dx] for each in limit]
            print('limits: ', limit)
            # store results in pandas dataframe
            results_1 = results_1.append(
                {
                    'X_U': x0,
                    'n_phase1': phasesAtEquilibriumMolarAmounts['LIQUID#1'],
                    'n_phase2':
                    phasesAtEquilibriumMolarAmounts['LIQUID_AUTO#2'],
                    'mu_U': mueq[1],
                    'mu_O': mueq[0],
                },
                ignore_index=True)
    results_1.to_csv('UO_muvsx_TAFID.csv')