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