def test_incompatible_db_ignores_with_kwarg_ignore(): "Symbol names too long for Thermo-Calc are ignored the database written as given." test_dbf = Database.from_string(INVALID_TDB_STR, fmt='tdb') with warnings.catch_warnings(record=True) as w: invalid_dbf = test_dbf.to_string(fmt='tdb', if_incompatible='ignore') not_expected_string_fragment = 'Ignoring that the following function names are beyond the 8 character TDB limit' assert all([not_expected_string_fragment not in str(warning.message) for warning in w]) assert test_dbf == Database.from_string(invalid_dbf, fmt='tdb')
def test_to_file_overwrites_with_if_exists_argument(): "Database.to_file should overwrite if 'overwrite' is passed to if_exists" fname = 'testwritedb.tdb' test_dbf = Database(ALNIPT_TDB) test_dbf.to_file(fname) # establish the initial file inital_modification_time = os.path.getmtime(fname) test_dbf.to_file(fname, if_exists='overwrite') # test if_exists behavior overwrite_modification_time = os.path.getmtime(fname) assert overwrite_modification_time > inital_modification_time
def test_incompatible_db_warns_by_default(): "Symbol names too long for Thermo-Calc warn and write the database as given by default." test_dbf = Database.from_string(INVALID_TDB_STR, fmt='tdb') with warnings.catch_warnings(record=True) as w: invalid_dbf = test_dbf.to_string(fmt='tdb') assert len(w) >= 1 expected_string_fragment = 'Ignoring that the following function names are beyond the 8 character TDB limit' assert any([expected_string_fragment in str(warning.message) for warning in w]) assert test_dbf == Database.from_string(invalid_dbf, fmt='tdb')
def test_incompatible_db_mangles_names_with_kwarg_fix(): "Symbol names too long for Thermo-Calc are mangled and replaced in symbol names, symbol expressions, and parameter expressions." test_dbf = Database.from_string(INVALID_TDB_STR, fmt='tdb') test_dbf_copy = deepcopy(test_dbf) mangled_dbf = Database.from_string(test_dbf.to_string(fmt='tdb', if_incompatible='fix'), fmt='tdb') # check that the long function name was hashed correctly a_very_long_function_name_hash_symbol = 'F' + str(hashlib.md5('A_VERY_LONG_FUNCTION_NAME'.encode('UTF-8')).hexdigest()).upper()[:7] assert a_very_long_function_name_hash_symbol in mangled_dbf.symbols.keys() assert 'COMPAT' in mangled_dbf.symbols.keys() # test that compatible keys are not removed assert test_dbf_copy == test_dbf # make sure test_dbf has not mutated assert test_dbf != mangled_dbf # also make sure test_dbf has not mutated
def test_tdb_species_are_parsed_correctly(): """The TDB speciescommand should be properly parsed.""" tdb_species_str = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT AL FCC_A1 2.6982E+01 4.5773E+03 2.8321E+01! ELEMENT O 1/2_MOLE_O2(G) 1.5999E+01 4.3410E+03 1.0252E+02! SPECIES AL+3 AL1/+3! SPECIES O-2 O1/-2! SPECIES O1 O! SPECIES O2 O2! SPECIES O3 O3! SPECIES AL1O1 AL1O1! SPECIES AL1O2 AL1O2! SPECIES AL2 AL2! SPECIES AL2O AL2O1! SPECIES AL2O1 AL2O1! SPECIES AL2O2 AL2O2! SPECIES AL2O3 AL2O3! SPECIES ALO AL1O1! SPECIES ALO2 AL1O2! SPECIES ALO3/2 AL1O1.5! """ test_dbf = Database.from_string(tdb_species_str, fmt='tdb') assert len(test_dbf.species) == 19 species_dict = {sp.name: sp for sp in test_dbf.species} assert species_dict['AL'].charge == 0 assert species_dict['O2'].constituents['O'] == 2 assert species_dict['O1'].constituents['O'] == 1 assert species_dict['AL1O2'].constituents['AL'] == 1 assert species_dict['AL1O2'].constituents['O'] == 2 assert species_dict['ALO3/2'].constituents['O'] == 1.5
def test_database_parameter_with_species_that_is_not_a_stoichiometric_formula(): """Species names used in parameters do not have to be stoichiometric formulas""" # names are taken from the Thermo-Calc documentation set, Database Manager Guide, SPECIES tdb_string = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT O 1/2_MOLE_O2(G) 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT SI HCP_A3 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT NA HCP_A3 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT SB RHOMBOHEDRAL_A7 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT H 1/2_MOLE_H2(G) 0.0000E+00 0.0000E+00 0.0000E+00! SPECIES SILICA SI1O2 ! $ tests for arbitrary names SPECIES NASB_6OH NA1SB1O6H6 ! $ tests for underscores SPECIES SB-3 SB/-3 ! $ tests for charge SPECIES ALCL2OH.3WATER AL1O1H1CL2H6O3 ! $ tests for charge PHASE LIQUID:L % 1 1.0 ! CONSTITUENT LIQUID:L : O, SI, NA, SB, H, SILICA, NASB_6OH, SB-3, ALCL2OH.3WATER : ! PARAMETER G(LIQUID,SILICA;0) 298.15 10; 3000 N ! PARAMETER G(LIQUID,NASB_6OH;0) 298.15 100; 3000 N ! PARAMETER G(LIQUID,ALCL2OH.3WATER;0) 298.15 1000; 3000 N ! PARAMETER G(LIQUID,SB-3;0) 298.15 10000; 3000 N ! """ dbf = Database.from_string(tdb_string, fmt='tdb') species_dict = {sp.name: sp for sp in dbf.species} species_names = list(species_dict.keys()) # check that the species are found assert 'SILICA' in species_names assert 'NASB_6OH' in species_names assert 'ALCL2OH.3WATER' in species_names assert 'SB-3' in species_names import tinydb silica = dbf._parameters.search(tinydb.where('constituent_array') == ((species_dict['SILICA'],),)) assert len(silica) == 1 assert silica[0]['parameter'].args[0][0] == 10 nasb_6oh = dbf._parameters.search(tinydb.where('constituent_array') == ((species_dict['NASB_6OH'],),)) assert len(nasb_6oh) == 1 assert nasb_6oh[0]['parameter'].args[0][0] == 100 alcl2oh_3water = dbf._parameters.search(tinydb.where('constituent_array') == ((species_dict['ALCL2OH.3WATER'],),)) assert len(alcl2oh_3water) == 1 assert alcl2oh_3water[0]['parameter'].args[0][0] == 1000 sbminus3 = dbf._parameters.search(tinydb.where('constituent_array') == ((species_dict['SB-3'],),)) assert len(sbminus3) == 1 assert sbminus3[0]['parameter'].args[0][0] == 10000
def test_binary_magnetic_reimported(): "Export and re-import a TDB before the calculation." dbf_imported = Database.from_string(DBF.to_string(fmt='tdb'), fmt='tdb') check_energy(Model(dbf_imported, ['CR', 'NI'], 'L12_FCC'), {v.T: 500, v.SiteFraction('L12_FCC', 0, 'CR'): 0.33, v.SiteFraction('L12_FCC', 0, 'NI'): 0.67, v.SiteFraction('L12_FCC', 1, 'CR'): 0.33, v.SiteFraction('L12_FCC', 1, 'NI'): 0.67}, -1.68840e4, mode='sympy')
def test_tdb_content_after_line_end_is_neglected(): """Any characters after the line ending '!' are neglected as in commercial software.""" tdb_line_ending_str = """$ Characters after line endings should be discarded. PARAMETER G(PH,A;0) 298.15 +42; 6000 N ! SHOULD_NOT_RAISE_ERROR $ G(PH,C;0) should not parse PARAMETER G(PH,B;0) 298.15 +9001; 6000 N ! PARAMETER G(PH,C;0) 298.15 +2; 600 N ! PARAMETER G(PH,D;0) 298.15 -42; 6000 N ! """ test_dbf = Database.from_string(tdb_line_ending_str, fmt='tdb') assert len(test_dbf._parameters) == 3
def test_writing_tdb_with_species_gives_same_result(): """Species defined in the tdb should be written back to the TDB correctly""" tdb_species_str = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT AL FCC_A1 2.6982E+01 4.5773E+03 2.8321E+01! ELEMENT O 1/2_MOLE_O2(G) 1.5999E+01 4.3410E+03 1.0252E+02! SPECIES AL+3 AL1/+3! SPECIES O-2 O1/-2! SPECIES O2 O2! SPECIES AL2 AL2! """ test_dbf = Database.from_string(tdb_species_str, fmt='tdb') written_tdb_str = test_dbf.to_string(fmt='tdb') test_dbf_reread = Database.from_string(written_tdb_str, fmt='tdb') assert len(test_dbf_reread.species) == 8 species_dict = {sp.name: sp for sp in test_dbf_reread.species} assert species_dict['AL'].charge == 0 assert species_dict['AL+3'].charge == 3 assert species_dict['O-2'].charge == -2
def run_test(): dbf = Database() dbf.elements = frozenset(['A']) dbf.add_phase('TEST', {}, [1]) dbf.add_phase_constituents('TEST', [['A']]) # add THETA parameters here dbf.add_parameter('THETA', 'TEST', [['A']], 0, 334.) conds = {v.T: np.arange(1.,800.,1), v.P: 101325} res = calculate(dbf, ['A'], 'TEST', T=conds[v.T], P=conds[v.P], model=EinsteinModel, output='testprop') #res_TE = calculate(dbf, ['A'], 'TEST', T=conds[v.T], P=conds[v.P], # model=EinsteinModel, output='einstein_temperature') import matplotlib.pyplot as plt plt.scatter(res['T'], res['testprop']) plt.xlabel('Temperature (K)') plt.ylabel('Molar Heat Capacity (J/mol-K)') plt.savefig('einstein.png') print(dbf.to_string(fmt='tdb'))
def test_species_are_parsed_in_tdb_phases_and_parameters(): """Species defined in the tdb phases and parameters should be parsed.""" tdb_str = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT AL FCC_A1 2.6982E+01 4.5773E+03 2.8321E+01! ELEMENT O 1/2_MOLE_O2(G) 1.5999E+01 4.3410E+03 1.0252E+02! SPECIES AL+3 AL1/+3! SPECIES O-2 O1/-2! SPECIES O2 O2! SPECIES AL2 AL2! PHASE TEST_PH % 1 1 ! CONSTITUENT TEST_PH :AL,AL2,O-2: ! PARA G(TEST_PH,AL;0) 298.15 +10; 6000 N ! PARA G(TEST_PH,AL2;0) 298.15 +100; 6000 N ! PARA G(TEST_PH,O-2;0) 298.15 +1000; 6000 N ! PHASE T2SL % 2 1 1 ! CONSTITUENT T2SL :AL+3:O-2: ! PARA L(T2SL,AL+3:O-2;0) 298.15 +2; 6000 N ! """ from tinydb import where test_dbf = Database.from_string(tdb_str, fmt='tdb') written_tdb_str = test_dbf.to_string(fmt='tdb') test_dbf_reread = Database.from_string(written_tdb_str, fmt='tdb') assert set(test_dbf_reread.phases.keys()) == {'TEST_PH', 'T2SL'} assert test_dbf_reread.phases['TEST_PH'].constituents[0] == {Species('AL'), Species('AL2'), Species('O-2')} assert len(test_dbf_reread._parameters.search(where('constituent_array') == ((Species('AL'),),))) == 1 assert len(test_dbf_reread._parameters.search(where('constituent_array') == ((Species('AL2'),),))) == 1 assert len(test_dbf_reread._parameters.search(where('constituent_array') == ((Species('O-2'),),))) == 1 assert test_dbf_reread.phases['T2SL'].constituents == ({Species('AL+3')}, {Species('O-2')}) assert len(test_dbf_reread._parameters.search(where('constituent_array') == ((Species('AL+3'),),(Species('O-2'),)))) == 1
def run_test(): dbf = Database() dbf.elements = frozenset(["A"]) dbf.add_phase("TEST", {}, [1]) dbf.add_phase_constituents("TEST", [["A"]]) # add THETA parameters here dbf.add_parameter("THETA", "TEST", [["A"]], 0, 334.0) conds = {v.T: np.arange(1.0, 800.0, 1), v.P: 101325} res = calculate(dbf, ["A"], "TEST", T=conds[v.T], P=conds[v.P], model=EinsteinModel, output="testprop") # res_TE = calculate(dbf, ['A'], 'TEST', T=conds[v.T], P=conds[v.P], # model=EinsteinModel, output='einstein_temperature') import matplotlib.pyplot as plt plt.scatter(res["T"], res["testprop"]) plt.xlabel("Temperature (K)") plt.ylabel("Molar Heat Capacity (J/mol-K)") plt.savefig("einstein.png")
def test_symbol_names_are_propagated_through_symbols_and_parameters(): """A map of old symbol names to new symbol names should propagate through symbol and parameter SymPy expressions""" tdb_propagate_str = """$ Mangled function names should propagate through other symbols and parameters ELEMENT A PH 0 0 0 ! FUNCTION FN1 298.15 -42; 6000 N ! FUNCTION FN2 298.15 FN1#; 6000 N ! PARAMETER G(PH,A;0) 298.15 FN1# + FN2#; 6000 N ! """ test_dbf = Database.from_string(tdb_propagate_str, fmt='tdb') rename_map = {'FN1': 'RENAMED_FN1', 'FN2': 'RENAMED_FN2'} _apply_new_symbol_names(test_dbf, rename_map) assert 'RENAMED_FN1' in test_dbf.symbols assert 'FN1' not in test_dbf.symbols # check that the old key was removed assert test_dbf.symbols['RENAMED_FN2'] == Piecewise((Symbol('RENAMED_FN1'), And(v.T < 6000.0, v.T >= 298.15)), (0, True)) assert test_dbf._parameters.all()[0]['parameter'] == Piecewise((Symbol('RENAMED_FN1')+Symbol('RENAMED_FN2'), And(v.T < 6000.0, v.T >= 298.15)), (0, True))
def test_tdb_species_with_charge_are_parsed_correctly(): """The TDB species that have a charge should be properly parsed.""" tdb_species_str = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT AL FCC_A1 2.6982E+01 4.5773E+03 2.8321E+01! ELEMENT O 1/2_MOLE_O2(G) 1.5999E+01 4.3410E+03 1.0252E+02! SPECIES AL+3 AL1/+3! SPECIES O-2 O1/-2! SPECIES O2 O2! SPECIES AL2 AL2! """ test_dbf = Database.from_string(tdb_species_str, fmt='tdb') assert len(test_dbf.species) == 8 species_dict = {sp.name: sp for sp in test_dbf.species} assert species_dict['AL'].charge == 0 assert species_dict['AL+3'].charge == 3 assert species_dict['O-2'].charge == -2
def test_lnprob_calculates_associate_tdb(datasets_db): """lnprob() successfully calculates the probability for equilibrium """ dbf = Database.from_string(CU_MG_TDB_ASSOC, fmt='tdb') datasets_db.insert(CU_MG_DATASET_ZPF_WORKING) comps = ['CU', 'MG', 'VA'] phases = ['LIQUID', 'FCC_A1', 'HCP_A3', 'LAVES_C15', 'CUMG2'] param = 'VV0001' orig_val = dbf.symbols[param].args[0] initial_params = {param: orig_val} zpf_kwargs = { 'zpf_data': get_zpf_data(dbf, comps, phases, datasets_db, initial_params), 'data_weight': 1.0, } opt = EmceeOptimizer(dbf) res = opt.predict([10], prior_rvs=[rv_zero()], symbols_to_fit=[param], zpf_kwargs=zpf_kwargs) assert np.isreal(res) assert not np.isinf(res) assert np.isclose(res, -31.309645520830344, rtol=1e-6) # The purpose of this part is to test that the driving forces (and probability) # are different than the case of VV0001 = 10. res_2 = opt.predict([-10000000], prior_rvs=[rv_zero()], symbols_to_fit=[param], zpf_kwargs=zpf_kwargs) assert np.isreal(res_2) assert not np.isinf(res_2) # Accept a large rtol becuase the results should be _very_ different assert not np.isclose(res_2, -31.309645520830344, rtol=1e-2)
def test_scheil_solidification_result_properties(): """Test that SolidificationResult objects produced by Scheil solidification have the required properties.""" # Required properties are # 1. the shape of the output arrays are matching # 2. the final fraction of solid is 1.0 and fraction of liquid is 0.0 # 3. the sum total of the final (solid) phase amounts is 1.0 dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) liquid_phase_name = 'LIQUID' initial_composition = {v.X('ZN'): 0.3} start_temperature = 850 sol_res = simulate_scheil_solidification(dbf, comps, phases, initial_composition, start_temperature, step_temperature=20.0) num_temperatures = len(sol_res.temperatures) assert num_temperatures == len(sol_res.x_liquid) assert num_temperatures == len(sol_res.fraction_liquid) assert num_temperatures == len(sol_res.fraction_solid) assert all( [num_temperatures == len(np) for np in sol_res.phase_amounts.values()]) # final phase amounts are correct assert sol_res.fraction_liquid[-1] == 0.0 assert sol_res.fraction_solid[-1] == 1.0 # total of final phase amounts is 1 assert np.isclose( np.sum([amnts[-1] for amnts in sol_res.phase_amounts.values()]), 1.0)
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 run_test(): dbf = Database('Fe-C_Fei_Brosh_2014_09.TDB') comps = ['FE', 'C', 'VA'] phases = ['FCC_A1', 'LIQUID'] conds = {v.T: 500, v.P: 101325, v.X('C'): 0.1} x0 = {'FE': 0.7, 'C': 0.3} a = 0 b = -10 * v.T slmod = SoluteTrapModel(dbf, comps, 'FCC_A1', a=a, b=b, n=1, x0=x0) # Use custom model for fcc; use default for all others models = {'FCC_A1': slmod} eq = equilibrium(dbf, comps, phases, conds, model=models) print(eq) res = calculate(dbf, comps, 'FCC_A1', T=conds[v.T], P=conds[v.P], model=models) res_nosoltrap = calculate(dbf, comps, 'FCC_A1', T=conds[v.T], P=conds[v.P]) import matplotlib.pyplot as plt plt.scatter(res.X.sel(component='C'), res.GM, c='r') plt.scatter(res.X.sel(component='C'), res_nosoltrap.GM, c='k') plt.xlabel('Mole Fraction C') plt.ylabel('Molar Gibbs Energy') plt.title('T = {} K'.format(conds[v.T])) plt.savefig('fcc_energy.png')
def test_lnprob_calculates_single_phase_probability_for_success(datasets_db): """lnprob() succesfully calculates the probability from single phase data""" dbf = Database.from_string(CU_MG_TDB_FCC_ONLY, fmt='tdb') datasets_db.insert(CU_MG_HM_MIX_SINGLE_FCC_A1) comps = ['CU', 'MG', 'VA'] phases = ['FCC_A1'] param = 'VV0003' orig_val = -14.0865 opt = EmceeOptimizer(dbf) thermochemical_data = get_thermochemical_data(dbf, comps, phases, datasets_db, symbols_to_fit=[param]) thermochemical_kwargs = {'thermochemical_data': thermochemical_data} res_orig = opt.predict([orig_val], prior_rvs=[rv_zero()], symbols_to_fit=[param], thermochemical_kwargs=thermochemical_kwargs) assert np.isreal(res_orig) assert np.isclose(res_orig, -9.119484935312146, rtol=1e-6) res_10 = opt.predict([10.0], prior_rvs=[rv_zero()], symbols_to_fit=[param], thermochemical_kwargs=thermochemical_kwargs) assert np.isreal(res_10) assert np.isclose(res_10, -9.143559131626864, rtol=1e-6) res_1e5 = opt.predict([1e5], prior_rvs=[rv_zero()], symbols_to_fit=[param], thermochemical_kwargs=thermochemical_kwargs) assert np.isreal(res_1e5) assert np.isclose(res_1e5, -1359.1335466316268, rtol=1e-6)
def test_unknown_format_from_file(): "from_string: Unknown import file format raises NotImplementedError." Database.from_string(ALCRNI_TDB, fmt='_fail_')
def test_load_from_stringio_from_file(): "Test database loading from a file-like object with the from_file method." test_tdb = Database.from_file(StringIO(ALCRNI_TDB), fmt='tdb') assert test_tdb == REFERENCE_DBF
def test_unspecified_format_from_string(): "from_string: Unspecified string format raises ValueError." Database.from_string(ALCRNI_TDB)
def test_export_import(): "Equivalence of re-imported database to original." test_dbf = Database.from_string(REFERENCE_DBF.to_string(fmt='tdb'), fmt='tdb') assert test_dbf == REFERENCE_DBF
import warnings import os import pytest from sympy import Symbol from numpy.testing import assert_allclose import numpy as np from pycalphad import Database, Model, calculate, equilibrium, EquilibriumError, ConditionError from pycalphad.codegen.callables import build_callables from pycalphad.core.solver import SolverBase, InteriorPointSolver from pycalphad.core.utils import get_state_variables import pycalphad.variables as v from pycalphad.tests.datasets import * warnings.simplefilter("always", UserWarning) # so we can test warnings ROSE_DBF = Database(ROSE_TDB) ALFE_DBF = Database(ALFE_TDB) ALNIFCC4SL_DBF = Database(ALNIFCC4SL_TDB) ALCOCRNI_DBF = Database(ALCOCRNI_TDB) ISSUE43_DBF = Database(ISSUE43_TDB) TOUGH_CHEMPOT_DBF = Database(ALNI_TOUGH_CHEMPOT_TDB) CUO_DBF = Database(CUO_TDB) PBSN_DBF = Database(PBSN_TDB) AL_PARAMETER_DBF = Database(AL_PARAMETER_TDB) CUMG_PARAMETERS_DBF = Database(CUMG_PARAMETERS_TDB) # ROSE DIAGRAM TEST def test_rose_nine(): "Nine-component rose diagram point equilibrium calculation." my_phases_rose = ['TEST']
def test_unspecified_format_from_file(): "from_file: Unspecified format for file descriptor raises ValueError." Database.from_file(StringIO(ALCRNI_TDB))
def test_comma_templims(): """Accept TEMPERATURE_LIMITS and default-limit commas.""" tdb_string = """ ELEMENT VA VACUUM 0.0 0.0 0.0 ! ELEMENT AL FCC_A1 26.98154 4540. 28.30 ! ELEMENT C GRAPHITE 12.011 1054.0 5.7423 ! ELEMENT CO HCP_A3 58.9332 4765.567 30.0400 ! ELEMENT CR BCC_A2 51.996 4050.0 23.5429 ! ELEMENT FE BCC_A2 55.847 4489.0 27.2797 ! ELEMENT MN CBCC_A12 54.9380 4995.696 32.2206 ! ELEMENT NI FCC_A1 58.69 4787.0 29.7955 ! TEMP-LIM 298 6000 ! $ ------------------------------------------------------------------------------ $ $ Fcc (cF4, Fm-3m) and MeX (cF8, Fm-3m) $ PHASE FCC_A1 %A 2 1 1 ! CONST FCC_A1 : AL% CO% CR FE% MN% NI% : C VA% : ! $ $ Disordered part of FCC_4SL, identical to FCC_A1 $ PHASE A1_FCC %A 2 1 1 ! CONST A1_FCC : AL CO CR FE MN NI : C VA% : ! $ $ Bcc (cI2, Im-3m) $ PHASE BCC_A2 %B 2 1 3 ! CONST BCC_A2 : AL CO CR% FE% MN% NI : C VA% : ! $ $ Disordered part of B2_BCC, identical to BCC_A2 (except Va) $ PHASE A2_BCC %B 2 1 3 ! CONST A2_BCC : AL CO CR FE MN NI VA : C VA% : ! $ $ Prototype CsCl (cP2, Pm-3m) $ PHASE B2_BCC %BO 3 0.5 0.5 3 ! CONST B2_BCC : AL CO CR FE MN% NI VA : AL CO CR FE MN NI% VA : C VA% : ! $ $ Hcp (hP2, P6_3/mmc) and Me2X (NiAs-type, hP4, P6_3/mmc, B8_1) $ PHASE HCP_A3 %A 2 1 0.5 ! CONST HCP_A3 : AL CO% CR FE MN NI : C VA% : ! $ ------------------------------------------------------------------------------ $ Defaults $ DEFINE-SYSTEM-DEFAULT ELEMENT 2 ! DEFAULT-COM DEFINE_SYSTEM_ELEMENT VA ! DEFAULT-COM REJECT_PHASE FCC_A1 BCC_A2 ! $DEFAULT-COM REJECT_PHASE A1_FCC FCC_4SL A2_BCC B2_BCC ! TYPE-DEF % SEQ * ! TYPE-DEF A GES AMEND_PHASE_DESCRIPTION @ MAGNETIC -3 0.28 ! TYPE-DEF B GES AMEND_PHASE_DESCRIPTION @ MAGNETIC -1 0.4 ! TYPE-DEF O GES AMEND_PHASE_DESCRIPTION B2_BCC DIS_PART A2_BCC ! TYPE-DEF Y GES AMEND_PHASE_DESCRIPTION FCC_4SL DIS_PART A1_FCC ! FUNCTION ZERO 298.15 0; 6000 N ! FUNCTION UN_ASS 298.15 0; 6000 N ! FUNCTION R 298.15 +8.31451; 6000 N ! $ ------------------------------------------------------------------------------ $ Element data $ ------------------------------------------------------------------------------ $ Al $ $ BCT_A5 and DIAMOND_A4 added in unary 3.0 $ PAR G(FCC_A1,AL:VA),, +GHSERAL; ,, N 91Din ! PAR G(A1_FCC,AL:VA),, +GHSERAL; , N 91Din ! PAR G(BCC_A2,AL:VA),, +GHSERAL+10083-4.813*T; 2900 N 91Din ! PAR G(A2_BCC,AL:VA),, +GHSERAL+10083-4.813*T; 2900 N 91Din ! PAR G(HCP_A3,AL:VA),, +GHSERAL+5481-1.8*T; 2900 N 91Din ! PAR G(CBCC_A12,AL:VA),, +GHSERAL +10083.4-4.813*T; 2900 N 91Din ! PAR G(CUB_A13,AL:VA),, +GHSERAL +10920.44-4.8116*T; 2900 N 91Din ! PAR G(BCT_A5,AL),, +GHSERAL+10083-4.813*T; 2900 N SGCOST ! PAR G(DIAMOND_A4,AL),, +GHSERAL+30*T; 2900 N SGCOST ! FUNCTION GHSERAL 298.15 -7976.15+137.093038*T-24.3671976*T*LN(T) -0.001884662*T**2-8.77664E-07*T**3+74092*T**(-1); 700.00 Y -11276.24+223.048446*T-38.5844296*T*LN(T) +0.018531982*T**2 -5.764227E-06*T**3+74092*T**(-1); 933.47 Y -11278.378+188.684153*T-31.748192*T*LN(T) -1.230524E+28*T**(-9); 2900.00 N ! """ dbf = Database.from_string(tdb_string, fmt='tdb') assert "AL" in dbf.elements
def test_database_parsing_of_floats_with_multiple_leading_zeros(): """Floats with multiple leading zeros should be properly parsed (gh-143)""" tdb_string = """$ The element has multiple leading zeros in '00.546' ELEMENT CU FCC_A1 00.546 5004.0 33.15 !""" dbf = Database.from_string(tdb_string, fmt='tdb') assert "CU" in dbf.elements
def test_database_parsing_of_floats_with_no_values_after_decimal(): """Floats with no values after the decimal should be properly parsed (gh-143)""" tdb_string = """$ The element has no values after the decimal in '5004.' ELEMENT CU FCC_A1 63.546 5004. 33.15 !""" dbf = Database.from_string(tdb_string, fmt='tdb') assert "CU" in dbf.elements
def test_to_file_raises_with_bad_if_exists_argument(): "Database.to_file should raise if a bad behavior string is passed to if_exists" fname = 'testwritedb.tdb' test_dbf = Database(ALNIPT_TDB) test_dbf.to_file(fname) # establish the initial file test_dbf.to_file(fname, if_exists='TEST_BAD_ARGUMENT') # test if_exists behavior
""" The equilibrium test module verifies that the Equilibrium class produces the correct solution for thermodynamic equilibrium. """ from unittest.case import SkipTest from nose.tools import raises from numpy.testing import assert_allclose from pycalphad import Database, calculate, equilibrium import pycalphad.variables as v from pycalphad.tests.datasets import ALNIPT_TDB, ROSE_TDB ROSE_DBF = Database(ROSE_TDB) ALFE_DBF = Database('examples/alfe_sei.TDB') # ROSE DIAGRAM TESTS # This will fail until the equilibrium engine is switched from Newton-Raphson @SkipTest def test_rose_nine(): "Nine-component rose diagram point equilibrium calculation." my_phases_rose = ['TEST'] comps = ['H', 'HE', 'LI', 'BE', 'B', 'C', 'N', 'O', 'F'] conds = dict({v.T: 1000, v.P: 101325}) for comp in comps[:-1]: conds[v.X(comp)] = 1.0 / float(len(comps)) eqx = equilibrium(ROSE_DBF, comps, my_phases_rose, conds) assert_allclose(eqx.GM.values.flat[0], -5.8351e3) # OTHER TESTS
def test_export_import(): "Equivalence of re-imported database to original." test_dbf = Database(ALNIPT_TDB) assert Database.from_string(test_dbf.to_string(fmt='tdb'), fmt='tdb') == test_dbf
def run_espei(run_settings): """Wrapper around the ESPEI fitting procedure, taking only a settings dictionary. Parameters ---------- run_settings : dict Dictionary of input settings Returns ------- Either a Database (for generate parameters only) or a tuple of (Database, sampler) """ run_settings = get_run_settings(run_settings) system_settings = run_settings['system'] output_settings = run_settings['output'] generate_parameters_settings = run_settings.get('generate_parameters') mcmc_settings = run_settings.get('mcmc') # handle verbosity verbosity = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG} logging.basicConfig(level=verbosity[output_settings['verbosity']]) # load datasets and handle i/o logging.debug('Loading and checking datasets.') dataset_path = system_settings['datasets'] datasets = load_datasets(sorted(recursive_glob(dataset_path, '*.json'))) if len(datasets.all()) == 0: logging.warning( 'No datasets were found in the path {}. This should be a directory containing dataset files ending in `.json`.' .format(dataset_path)) logging.debug('Finished checking datasets') with open(system_settings['phase_models']) as fp: phase_models = json.load(fp) if generate_parameters_settings is not None: refdata = generate_parameters_settings['ref_state'] excess_model = generate_parameters_settings['excess_model'] dbf = generate_parameters(phase_models, datasets, refdata, excess_model) dbf.to_file(output_settings['output_db'], if_exists='overwrite') if mcmc_settings is not None: tracefile = output_settings['tracefile'] probfile = output_settings['probfile'] # check that the MCMC output files do not already exist # only matters if we are actually running MCMC if os.path.exists(tracefile): raise OSError( 'Tracefile "{}" exists and would be overwritten by a new run. Use the ``output.tracefile`` setting to set a different name.' .format(tracefile)) if os.path.exists(probfile): raise OSError( 'Probfile "{}" exists and would be overwritten by a new run. Use the ``output.probfile`` setting to set a different name.' .format(probfile)) # scheduler setup if mcmc_settings['scheduler'] == 'MPIPool': # check that cores is not an input setting if mcmc_settings.get('cores') != None: logging.warning("MPI does not take the cores input setting.") from emcee.utils import MPIPool # code recommended by emcee: if not master, wait for instructions then exit client = MPIPool() if not client.is_master(): logging.debug( 'MPIPool is not master. Waiting for instructions...') client.wait() sys.exit(0) logging.info("Using MPIPool on {} MPI ranks".format(client.size)) elif mcmc_settings['scheduler'] == 'dask': from distributed import LocalCluster cores = mcmc_settings.get('cores', multiprocessing.cpu_count()) if (cores > multiprocessing.cpu_count()): cores = multiprocessing.cpu_count() logging.warning( "The number of cores chosen is larger than available. " "Defaulting to run on the {} available cores.".format( cores)) scheduler = LocalCluster(n_workers=cores, threads_per_worker=1, processes=True) client = ImmediateClient(scheduler) client.run(logging.basicConfig, level=verbosity[output_settings['verbosity']]) logging.info("Running with dask scheduler: %s [%s cores]" % (scheduler, sum(client.ncores().values()))) try: logging.info( "bokeh server for dask scheduler at localhost:{}".format( client.scheduler_info()['services']['bokeh'])) except KeyError: logging.info("Install bokeh to use the dask bokeh server.") elif mcmc_settings['scheduler'] == 'emcee': from emcee.interruptible_pool import InterruptiblePool cores = mcmc_settings.get('cores', multiprocessing.cpu_count()) if (cores > multiprocessing.cpu_count()): cores = multiprocessing.cpu_count() logging.warning( "The number of cores chosen is larger than available. " "Defaulting to run on the {} available cores.".format( cores)) client = InterruptiblePool(processes=cores) logging.info("Using multiprocessing on {} cores".format(cores)) elif mcmc_settings['scheduler'] == 'None': client = None logging.info( "Not using a parallel scheduler. ESPEI is running MCMC on a single core." ) # get a Database if mcmc_settings.get('input_db'): dbf = Database(mcmc_settings.get('input_db')) # load the restart chain if needed if mcmc_settings.get('restart_chain'): restart_chain = np.load(mcmc_settings.get('restart_chain')) else: restart_chain = None # load the remaning mcmc fitting parameters mcmc_steps = mcmc_settings.get('mcmc_steps') save_interval = mcmc_settings.get('mcmc_save_interval') chains_per_parameter = mcmc_settings.get('chains_per_parameter') chain_std_deviation = mcmc_settings.get('chain_std_deviation') deterministic = mcmc_settings.get('deterministic') dbf, sampler = mcmc_fit( dbf, datasets, scheduler=client, mcmc_steps=mcmc_steps, chains_per_parameter=chains_per_parameter, chain_std_deviation=chain_std_deviation, save_interval=save_interval, tracefile=tracefile, probfile=probfile, restart_chain=restart_chain, deterministic=deterministic, ) dbf.to_file(output_settings['output_db'], if_exists='overwrite') # close the scheduler, if possible if hasattr(client, 'close'): client.close() return dbf, sampler return dbf
def test_load_from_stringio(): "Test database loading from a file-like object." test_tdb = Database.from_file(StringIO(ALCRNI_TDB), fmt='tdb') assert test_tdb == REFERENCE_DBF
def test_to_file_defaults_to_raise_if_exists(): "Attempting to use Database.to_file should raise by default if it exists" fname = 'testwritedb.tdb' test_dbf = Database(ALNIPT_TDB) test_dbf.to_file(fname) # establish the initial file test_dbf.to_file(fname) # test if_exists behavior
def test_incompatible_db_raises_error_with_kwarg_raise(): "Symbol names too long for Thermo-Calc raise error on write with kwarg raise." test_dbf = Database.from_string(INVALID_TDB_STR, fmt='tdb') test_dbf.to_string(fmt='tdb', if_incompatible='raise')
def test_database_diffusion(): "Diffusion database support." assert Database(DIFFUSION_TDB).phases == \ Database.from_string(Database(DIFFUSION_TDB).to_string(fmt='tdb'), fmt='tdb').phases
def test_export_import(): "Equivalence of Model using re-imported database." test_model = Model(Database.from_string(ALCRNI_DBF.to_string(fmt='tdb'), fmt='tdb'), ['CR', 'NI'], 'L12_FCC') ref_model = Model(ALCRNI_DBF, ['CR', 'NI'], 'L12_FCC') assert test_model == ref_model
""" The utils test module contains tests for pycalphad utilities. """ from pycalphad import Database, Model from pycalphad.core.utils import filter_phases, unpack_components, instantiate_models from pycalphad.tests.datasets import ALNIPT_TDB ALNIPT_DBF = Database(ALNIPT_TDB) def test_filter_phases_removes_disordered_phases_from_order_disorder(): """Databases with order-disorder models should have the disordered phases be filtered.""" all_phases = set(ALNIPT_DBF.phases.keys()) filtered_phases = set( filter_phases(ALNIPT_DBF, unpack_components(ALNIPT_DBF, ['AL', 'NI', 'PT', 'VA']))) assert all_phases.difference(filtered_phases) == {'FCC_A1'} def test_filter_phases_removes_phases_with_inactive_sublattices(): """Phases that have no active components in any sublattice should be filtered""" all_phases = set(ALNIPT_DBF.phases.keys()) filtered_phases = set( filter_phases(ALNIPT_DBF, unpack_components(ALNIPT_DBF, ['AL', 'NI', 'VA']))) assert all_phases.difference(filtered_phases) == { 'FCC_A1', 'PT8AL21', 'PT5AL21', 'PT2AL', 'PT2AL3', 'PT5AL3', 'ALPT2' }
def test_export_import(): "Equivalence of re-imported database to original." test_dbf = Database(ALNIPT_TDB) assert Database.from_string(test_dbf.to_string(fmt='tdb', if_incompatible='ignore'), fmt='tdb') == test_dbf test_dbf = Database(ALFE_TDB) assert Database.from_string(test_dbf.to_string(fmt='tdb'), fmt='tdb') == test_dbf
# Remove extra whitespace inside line splitlines = [' '.join(k.split()) for k in splitlines] # Remove comments splitlines = [k.strip().split('$', 1)[0] for k in splitlines] # Combine everything back together lines = ' '.join(splitlines) # Now split by the command delimeter commands = lines.split('!') # Filter out comments one more time # It's possible they were at the end of a command commands = [k.strip() for k in commands if not k.startswith("$")] # Temporary storage while we process type definitions dbf.tdbtypedefs = {} for command in commands: if len(command) == 0: continue tokens = None try: tokens = _tdb_grammar().parseString(command) _TDB_PROCESSOR[tokens[0]](dbf, *tokens[1:]) except ParseException: print("Failed while parsing: " + command) print("Tokens: " + str(tokens)) raise del dbf.tdbtypedefs Database.register_format("tdb", read=read_tdb, write=write_tdb)
from StringIO import StringIO except ImportError: # Python 3 from io import StringIO warnings.simplefilter("always", UserWarning) # so we can test warnings # # DATABASE LOADING TESTS # # This uses the backwards compatible behavior # Underneath it's calling many of the same routines, so we can't guarantee # the Database is correct; that's okay, other tests check correctness. # We're only checking consistency and exercising error checking here. REFERENCE_DBF = Database(ALCRNI_TDB) REFERENCE_MOD = Model(REFERENCE_DBF, ['CR', 'NI'], 'L12_FCC') INVALID_TDB_STR = """$ Note: database that invalidates the minimum compatibility subset for TDBs in different softwares $ functions names must be <=8 characters (Thermo-Calc) FUNCTION A_VERY_LONG_FUNCTION_NAME 298.15 -42; 6000 N ! FUNCTION COMPAT 298.15 +9001; 6000 N ! """ def test_database_eq(): "Database equality comparison." test_dbf = Database(ALCRNI_TDB) assert test_dbf == test_dbf assert test_dbf == REFERENCE_DBF assert not (test_dbf == Database(ROSE_TDB))
def test_load_from_stringio(): "Test database loading from a file-like object." test_tdb = Database(StringIO(ALCRNI_TDB)) assert test_tdb == REFERENCE_DBF
from sympy.functions.elementary.complexes import Abs from sympy.functions.elementary.exponential import exp, log from sympy.functions.elementary.trigonometric import tanh from sympy.parsing.sympy_parser import parse_expr from sympy.solvers import solve, solve_linear_system from sympy.utilities.codegen import codegen # Thermodynamic information from pycalphad import Database, Model from constants import * interpolator = x**3 * (6.*x**2 - 15.*x + 10.) dinterpdx = 30. * x**2 * (1.0 - x)**2 # Read CALPHAD database from disk, specify phases and elements of interest tdb = Database('Du_Cr-Nb-Ni_simple.tdb') elements = ['CR', 'NB', 'NI'] species = list(set([i for c in tdb.phases['FCC_A1'].constituents for i in c])) model = Model(tdb, species, 'FCC_A1') g_gamma = parse_expr(str(model.ast)) species = list(set([i for c in tdb.phases['D0A_NBNI3'].constituents for i in c])) model = Model(tdb, species, 'D0A_NBNI3') g_delta = parse_expr(str(model.ast)) species = list(set([i for c in tdb.phases['C14_LAVES'].constituents for i in c])) model = Model(tdb, species, 'C14_LAVES') g_laves = parse_expr(str(model.ast))
def test_load_from_string(): "Test database loading from a string." test_model = Model(Database.from_string(ALCRNI_TDB, fmt='tdb'), ['CR', 'NI'], 'L12_FCC') assert test_model == REFERENCE_MOD
def test_tdb_missing_terminator_element(): tdb_str = """$ Note missing '!' in next line ELEMENT ZR BCT_A5 FUNCTION EMBCCTI 298.15 -39.72; 6000 N !""" Database(tdb_str)
""" The test_model module contains unit tests for the Model object. """ from __future__ import print_function from pycalphad import Database, Model, CompiledModel from pycalphad.tests.datasets import ALCRNI_TDB, ALNIPT_TDB import numpy as np ALCRNI_DBF = Database(ALCRNI_TDB) ALNIPT_DBF = Database(ALNIPT_TDB) def test_model_eq(): "Model equality comparison." test_model = Model(ALCRNI_DBF, ['AL', 'CR'], 'L12_FCC') assert test_model == test_model assert test_model == Model(ALCRNI_DBF, ['AL', 'CR'], 'L12_FCC') assert not (test_model == Model(ALCRNI_DBF, ['NI', 'CR'], 'L12_FCC')) # literals which don't have __dict__ assert not (test_model == 42) assert not (test_model == None) assert not (42 == test_model) assert not (None == test_model) def test_model_ne(): "Model inequality comparison." test_model = Model(ALCRNI_DBF, ['AL', 'CR'], 'L12_FCC') assert not (test_model != test_model) assert not (test_model != Model(ALCRNI_DBF, ['AL', 'CR'], 'L12_FCC')) assert test_model != Model(ALCRNI_DBF, ['NI', 'CR'], 'L12_FCC')
def test_database_parameter_with_species_that_is_not_a_stoichiometric_formula( ): """Species names used in parameters do not have to be stoichiometric formulas""" # names are taken from the Thermo-Calc documentation set, Database Manager Guide, SPECIES tdb_string = """ ELEMENT /- ELECTRON_GAS 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT O 1/2_MOLE_O2(G) 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT SI HCP_A3 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT NA HCP_A3 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT SB RHOMBOHEDRAL_A7 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT H 1/2_MOLE_H2(G) 0.0000E+00 0.0000E+00 0.0000E+00! SPECIES SILICA SI1O2 ! $ tests for arbitrary names SPECIES NASB_6OH NA1SB1O6H6 ! $ tests for underscores SPECIES SB-3 SB/-3 ! $ tests for charge SPECIES ALCL2OH.3WATER AL1O1H1CL2H6O3 ! $ tests for charge PHASE LIQUID:L % 1 1.0 ! CONSTITUENT LIQUID:L : O, SI, NA, SB, H, SILICA, NASB_6OH, SB-3, ALCL2OH.3WATER : ! PARAMETER G(LIQUID,SILICA;0) 298.15 10; 3000 N ! PARAMETER G(LIQUID,NASB_6OH;0) 298.15 100; 3000 N ! PARAMETER G(LIQUID,ALCL2OH.3WATER;0) 298.15 1000; 3000 N ! PARAMETER G(LIQUID,SB-3;0) 298.15 10000; 3000 N ! """ dbf = Database.from_string(tdb_string, fmt='tdb') species_dict = {sp.name: sp for sp in dbf.species} species_names = list(species_dict.keys()) # check that the species are found assert 'SILICA' in species_names assert 'NASB_6OH' in species_names assert 'ALCL2OH.3WATER' in species_names assert 'SB-3' in species_names import tinydb silica = dbf._parameters.search( tinydb.where('constituent_array') == ((species_dict['SILICA'], ), )) assert len(silica) == 1 assert silica[0]['parameter'].args[0][0] == 10 nasb_6oh = dbf._parameters.search( tinydb.where('constituent_array') == ((species_dict['NASB_6OH'], ), )) assert len(nasb_6oh) == 1 assert nasb_6oh[0]['parameter'].args[0][0] == 100 alcl2oh_3water = dbf._parameters.search( tinydb.where('constituent_array') == (( species_dict['ALCL2OH.3WATER'], ), )) assert len(alcl2oh_3water) == 1 assert alcl2oh_3water[0]['parameter'].args[0][0] == 1000 sbminus3 = dbf._parameters.search( tinydb.where('constituent_array') == ((species_dict['SB-3'], ), )) assert len(sbminus3) == 1 assert sbminus3[0]['parameter'].args[0][0] == 10000
The calculate test module verifies that calculate() calculates Model quantities correctly. """ import nose.tools from pycalphad import Database, calculate try: # Python 2 from StringIO import StringIO except ImportError: # Python 3 from io import StringIO from pycalphad.tests.datasets import ALCRNI_TDB as TDB_TEST_STRING DBF = Database(TDB_TEST_STRING) def test_surface(): "Bare minimum: calculation produces a result." calculate(DBF, ['AL', 'CR', 'NI'], 'L12_FCC', T=1273., mode='numpy') @nose.tools.raises(AttributeError) def test_unknown_model_attribute(): "Sampling an unknown model attribute raises exception." calculate(DBF, ['AL', 'CR', 'NI'], 'L12_FCC', T=1400.0, output='_fail_') def test_statevar_upcast(): "Integer state variable values are cast to float."
""" The energy test module verifies that the Model class produces the correct abstract syntax tree for the energy. """ import nose.tools from pycalphad import Database, Model from pycalphad.core.utils import make_callable from pycalphad.tests.datasets import ALCRNI_TDB import pycalphad.variables as v import numpy as np DBF = Database(ALCRNI_TDB) @nose.tools.raises(ValueError) def test_sympify_safety(): "Parsing malformed strings throws exceptions instead of executing code." from pycalphad.io.tdb import _sympify_string teststr = "().__class__.__base__.__subclasses__()[216]('ls')" _sympify_string(teststr) # should throw ParseException def calculate_energy(model, variables, mode='numpy'): """ Calculate the value of the energy at a point. Parameters ---------- model, Model Energy model for a phase.