def setUp(self):
     print(
         "### test Ni-Al-Cr coherent interface between FCC_A1 and GAMMA_PRIME phases ###\n"
     )
     # tdb filepath
     self.__tdbFile = os.environ['TDBDATA'] + '/NiAlCrHuang1999.tdb'
     # components
     self.__comps = ['NI', 'AL', 'CR', 'VA']
     CoherentGibbsEnergy_OC.initOC(self.__tdbFile, self.__comps)
 def setUp(self):
     print("### test U-O-Zr coherent interface in the liquid miscibility gap ###\n")
     # tdb filepath
     self.__tdbFile=os.environ['TDBDATA_PRIVATE']+'/feouzr.tdb'
     # components
     self.__comps = ['O', 'U', 'ZR']
     # mass density laws (from Barrachin2004)
     self.__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
     }
     CoherentGibbsEnergy_OC.initOC(self.__tdbFile, self.__comps)
 def test_MolarVolume(self):
     print("### test_MolarVolume ###\n")
     # phase names
     phasenames = ['LIQUID']
     # temperature and pressure
     T = 2000.00
     P = 1E5
     # instantiate OC "model"
     model = CoherentGibbsEnergy_OC(T, P, phasenames, self.__verbosity)
     # calculate and compare chemical potentials
     x = [0.343298, 0.241778]   # U, Zr content (component molar fractions excluding the first one in comps)
     #
     functions=model.constantPartialMolarVolumeFunctions(x, self.__constituentDensityLaws)
     np.testing.assert_allclose(0.005934892340596354*1E-3, functions[0](x), rtol=1e-6, atol=1E-6)
     np.testing.assert_allclose(0.015389805158548542*1E-3, functions[1](x), rtol=1e-6, atol=1E-6)
     np.testing.assert_allclose(0.012607127224985304*1E-3, functions[2](x), rtol=1e-6, atol=1E-6)
 def test_SinglePhaseEquilibrium(self):
     print("### test_SinglePhaseEquilibrium ###\n")
     # phase names
     phasenames = ['FCC_A1', 'GAMMA_PRIME']
     # temperature and pressure
     T = 1273.0
     P = 1E5
     for phasename in phasenames:
         # instantiate two-phase OC "model"
         if (phasename == 'GAMMA_PRIME'):
             model_OC = CoherentGibbsEnergy_OC(
                 T, P, phasename, self.__verbosity, True
             )  # enforce gridminizer even in this 'local' equilibrium in order to reproduce same results as pyCalphad
         else:
             model_OC = CoherentGibbsEnergy_OC(T, P, phasename,
                                               self.__verbosity, False)
         # instantiate two-phase pyCalphad "model"
         model = CoherentGibbsEnergy(T, Database(self.__tdbFile),
                                     self.__comps, phasename)
         # calculate and compare chemical potentials
         x = [
             0.1931, 0.008863
         ]  # Al, Cr content (component molar fractions excluding the first one in comps)
         G = model.Gibbsenergy(x)
         mu = model.chemicalpotential(x)
         print('pyCalphad: ', G, mu)
         mu_OC = model_OC.chemicalpotential(x)
         G_OC = model_OC.getGibbsEnergy()
         print('OpenCalphad:', G_OC, mu_OC)
         np.testing.assert_array_almost_equal(G, G_OC, decimal=2)
         np.testing.assert_array_almost_equal(mu, mu_OC, decimal=1)
 def test_TwoPhaseEquilibrium(self):
     print("### test_TwoPhaseEquilibrium ###\n")
     # phase names
     phasenames = ['FCC_A1', 'GAMMA_PRIME']
     # temperature and pressure
     T = 1273.0
     P = 1E5
     # instantiate two-phase OC "model"
     model_OC = CoherentGibbsEnergy_OC(T, P, phasenames, self.__verbosity)
     # instantiate two-phase pyCalphad "model"
     model = CoherentGibbsEnergy(T, Database(self.__tdbFile), self.__comps,
                                 phasenames)
     # calculate and compare chemical potentials
     x = [
         0.18, 0.0081
     ]  # Al, Cr content (component molar fractions excluding the first one in comps)
     G = model.Gibbsenergy(x)
     mu = model.chemicalpotential(x)
     print('pyCalphad: ', G, mu)
     mu_OC = model_OC.chemicalpotential(x)
     G_OC = model_OC.getGibbsEnergy()
     print('OpenCalphad:', G_OC, mu_OC)
     np.testing.assert_array_almost_equal(G, G_OC, decimal=2)
     np.testing.assert_array_almost_equal(mu, mu_OC, decimal=1)
    def test_WithSigmaCoherent(self):
        print("### test_WithSigmaCoherent ###\n")
        # phase names
        phasenames = ['LIQUID', 'LIQUID']
        # temperature and pressure
        T = 3000.0
        P = 1E5
        # Given initial alloy composition. x0 is the mole fraction of U and Zr.
        x0 = [0.343298, 0.241778]
        # Molar volumes of pure components evaluated at x0 and kept constant afterwards
        model = CoherentGibbsEnergy_OC(T, P, phasenames[0], self.__verbosity)
        functions=model.constantPartialMolarVolumeFunctions(x0, self.__constituentDensityLaws, 1E-5)
        # Composition range for searching initial interfacial equilibrium composition.
        limit = [0.0001, 0.9]
        # Composition step for searching initial interfacial equilibrium composition.
        dx = 0.1

        # calculate interfacial energy
        sigma = SigmaCoherent_OC(
            T=T,
            x0=x0,
            db=self.__tdbFile,
            comps=self.__comps,
            phasenames=phasenames,
            purevms=functions,
            limit=limit,
            dx=dx,
            enforceGridMinimizerForLocalEq=False
        )

        # Print the calculated interfacial energy with xarray.Dataset type.
        print(sigma, "\n")
        # Print the calculated interfacial energy with xarray.DataArray type.
        print(sigma.Interfacial_Energy, "\n")
        # Print the calculated interfacial energy value.
        print(sigma.Interfacial_Energy.values, "\n")
def calculateChemicalPotentialsAndMolarVolumes(tdbFile, suffix):
    print('### calculate U-O chemical potentials ###\n')
    # 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
    # temperature
    T = 2800
    # composition range (mole fraction of U)
    xmin = 0.4
    xmax = 0.99
    xRange = np.linspace(xmin, xmax, num=60, endpoint=True)

    results = pd.DataFrame(
        columns=['xU', 'muU', 'muO', 'VmU', 'VmO', 'mueqU', 'mueqO'])

    # calculate global equilibrium and retrieve associated chemical potentials
    CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
    model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
    mueq = model.chemicalpotential([0.5 * (xmin + xmax)])

    # calculate single-phase equilibrium and retrieve associated chemical potentials
    CoherentGibbsEnergy_OC.initOC(tdbFile, comps)
    model = CoherentGibbsEnergy_OC(T, 1E5, phasenames[0])
    for x in xRange:
        print('**** xU=', x)
        mu = model.chemicalpotential([x])
        gibbsEnergy = oc.getScalarResult('G') / oc.getScalarResult('N')
        if ('TAF' in tdbFile):
            functions = model.constantPartialMolarVolumeFunctions(
                [x], constituentDensityLaws, 1E-5,
                constituentToEndmembersConverter)
        else:
            functions = model.constantPartialMolarVolumeFunctions(
                [x], constituentDensityLaws, 1E-5)
        results = results.append(
            {
                'xU': x,
                'muU': mu[1],
                'muO': mu[0],
                'VmU': functions[1](T),
                'VmO': functions[0](T),
                'mueqU': mueq[1],
                'mueqO': mueq[0],
                'Gm': gibbsEnergy
            },
            ignore_index=True)
    # write csv result file
    results.to_csv('macro_liquidMG_UO_chemicalPotentialsAndMolarVolumes' +
                   suffix + '.csv')
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')
def SigmaCoherent_OC(T,
                     x0,
                     db,
                     comps,
                     phasenames,
                     purevms,
                     limit=[0, 1.0],
                     bulkX=None,
                     dx=0.01,
                     enforceGridMinimizerForLocalEq=False,
                     mueq=None):
    """
    Calculate the coherent interfacial energy in alloys.

    Parameters
    -----------
    T: float
        Given temperature.
    x0: list
        Initial alloy composition.
    db : Database
        Database containing the relevant parameters.
    comps : list
        Names of components to consider in the calculation.
    phasenames : list
        Names of phase model to build.
    purevms: list
        The molar volumes of pure components (expression) or the interfacial molar volumes (functions)
    limit: list
        The limit of composition for searching interfacial composition in equilibrium.
    bulkX: list of list
        The list of compositions of the bulk phases in equilibrium.
    dx: float
        The step of composition for searching interfacial composition in equilibrium.
    enforceGridMinimizerForLocalEq: boolean
        A flag to enforce the use of the gridminimzer in the calculation of the chemical potential of the interface for a given component composition
    mueq: list
        Bulk chemical potential for the different components

    Returns:
    -----------
    Components:list of str
        Given components.
    Temperature: float
        Given temperature.
    Initial_Alloy_Composition: list
        Given initial alloy composition.
    Interfacial_Composition: list
        Interfacial composition of the grid minimization.
    Partial_Interfacial_Energies: list
        Partial interfacial energies of components.
    Interfacial_Energy: float
        Requested interfacial energies.

    Return type: xarray Dataset
    """
    if (type(purevms[0]) == list):
        # the molar volumes of pure components are given as expressions (original openIEC implementation)
        phasevm = [
            MolarVolume(Database(db), phasenames[i], comps, purevms[i])
            for i in range(2)
        ]
        _vmis = InterficialMolarVolume(*phasevm)
        """decorate the _vmis to release the constains on temperature"""
        vmis = [wraptem(T, f) for f in _vmis]
    else:
        # the molar volumes of pure components directly given as functions
        vmis = purevms
    """Chemical potentials in two-phase equilibrium"""
    if (mueq == None):
        CoherentGibbsEnergy_OC.initOC(db, comps)
        model = CoherentGibbsEnergy_OC(T, 1E5, phasenames)
        mueq = model.chemicalpotential(x0)
    """Chemical potentials in two bulk phases"""
    CoherentGibbsEnergy_OC.initOC(db, comps)
    model_phase = [
        CoherentGibbsEnergy_OC(T, 1E5, phasenames[i], False,
                               enforceGridMinimizerForLocalEq)
        for i in range(len(phasenames))
    ]
    alphafuncs, betafuncs = [each.chemicalpotential for each in model_phase]

    sigma_model = SigmaCoherentInterface(alphafuncs, betafuncs, mueq, vmis)

    components = [each for each in comps if each != "VA"]
    cum = int(len(components) - 1)
    print(
        "\n******************************************************************************\nOpenIEC is looking for interfacial equilibirium composition with OpenCalphad.\nFor more information visit https://github.com/niamorelreillet/openiec_with_OC."
    )
    limits = limit.copy()
    if (type(limits[0]) != list):
        limits = [limits] * cum
    x_s = SearchEquilibrium(sigma_model.objective, limits, [dx] * cum, bulkX)
    x_c = ComputeEquilibrium(sigma_model.objective, x_s["x"])

    print(
        "******************************************************************************\n"
    )
    sigma = sigma_model.infenergy(x_c)

    xx0 = [1.0 - sum(list(x0))] + list(x0)
    xx_c = [1.0 - sum(list(x_c))] + list(x_c)
    sigmapartial = list(np.array(sigma).flatten())
    sigmaavg = np.average([each for each in sigma])

    res = Dataset({
        "Components": components,
        "Temperature": T,
        "Initial_Alloy_Composition": ("Components", xx0),
        "Interfacial_Composition": ("Components", xx_c),
        "Partial_Interfacial_Energy": ("Components", sigmapartial),
        "Interfacial_Energy": sigmaavg,
    })

    return res
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')