def test_mole_and_mass_fraction_conversions(): """Test mole <-> mass conversions work as expected.""" # Passing database as a mass dict works dbf = Database(CUO_TDB) mole_fracs = {v.X('O'): 0.5} mass_fracs = v.get_mass_fractions(mole_fracs, v.Species('CU'), dbf) assert np.isclose(mass_fracs[v.W('O')], 0.20113144) # TC # Conversion back works round_trip_mole_fracs = v.get_mole_fractions(mass_fracs, 'CU', dbf) assert all( np.isclose(round_trip_mole_fracs[mf], mole_fracs[mf]) for mf in round_trip_mole_fracs.keys()) # Using Thermo-Calc's define components to define Al2O3 and TiO2 # Mass dict defined by hand md = {'AL': 26.982, 'TI': 47.88, 'O': 15.999} alumina = v.Species('AL2O3') mass_fracs = {v.W(alumina): 0.81, v.W("TIO2"): 0.13} mole_fracs = v.get_mole_fractions(mass_fracs, 'O', md) assert np.isclose(mole_fracs[v.X('AL2O3')], 0.59632604) # TC assert np.isclose(mole_fracs[v.X('TIO2')], 0.12216562) # TC # Conversion back works round_trip_mass_fracs = v.get_mass_fractions(mole_fracs, v.Species('O'), md) assert all( np.isclose(round_trip_mass_fracs[mf], mass_fracs[mf]) for mf in round_trip_mass_fracs.keys())
def test_eq_overdetermined_comps(): """ The number of composition conditions should yield exactly one dependent component. This is the overdetermined case. """ equilibrium(ALFE_DBF, ['AL', 'FE'], 'LIQUID', {v.T: 2000, v.P: 101325, v.X('FE'): 0.2, v.X('AL'): 0.8})
def test_eq_charge_ndzro(): """Nd-Zr-O system (with charged species) are correctly calculated and charged balanced in equilibrium""" comps = ['ND', 'ZR', 'O', 'VA'] phases = ['ND2O3_A', 'PYRO'] conds = {v.P: 101325, v.N: 1, v.T: 1400, v.X('ND'): 0.25, v.X('O'): 0.625} # Higher point density is required for convergence. Lower point densities # Can result in either no phases, or only FLUO phase (incorrect) res = equilibrium(AL2O3_ND2O3_ZRO2_DBF, comps, phases, conds, verbose=True) # Values below checked with Thermo-Calc assert np.isclose(-432325.423784, res.GM.values.squeeze()) assert np.all(res.Phase.values.squeeze() == np.array(['ND2O3_A', 'PYRO', '', ''])) assert np.allclose(res.NP.values.squeeze()[:2], [0.30164254, 0.69835646]) # site fractions of ND2O3_A Y_ND2O3_A = res.Y.values.squeeze()[0, :5] Y_PYRO = res.Y.values.squeeze()[1, :] SPEC_CHG_ND2O3_A = np.array([3, 4, -2, -2, 0]) # (ND+3,ZR+4):(O-2):(O-2,VA) SITE_RATIO_ND2O3_A = np.array([2, 2, 3, 1, 1]) # 2:3:1 SPEC_CHG_PYRO = np.array([3, 4, 3, 4, -2, 0, -2, -2, 0]) # (ND+3,ZR+4):(ND+3,ZR+4):(O-2,VA):(O-2):(O-2,VA) SITE_RATIO_PYRO = np.array([2, 2, 2, 2, 6, 6, 1, 1, 1]) # 2:2:6:1:1 CHG_ND2O3_A = np.dot(Y_ND2O3_A*SPEC_CHG_ND2O3_A, SITE_RATIO_ND2O3_A) CHG_PYRO = np.dot(Y_PYRO*SPEC_CHG_PYRO, SITE_RATIO_PYRO) print('CHARGE ND2O3_A', CHG_ND2O3_A) print('CHARGE PYRO', CHG_PYRO) assert np.isclose(CHG_ND2O3_A, 0) assert np.isclose(CHG_PYRO, 0) assert np.allclose(Y_ND2O3_A, [9.79497936e-01, 2.05020639e-02, 1.00000000e+00, 2.05020639e-02, 9.79497936e-01], rtol=5e-4) assert np.allclose(Y_PYRO, [9.99970071e-01, 2.99288042e-05, 3.83395063e-02, 9.61660494e-01, 9.93381787e-01, 6.61821340e-03, 1.00000000e+00, 1.39970285e-03, 9.98600297e-01], rtol=5e-4)
def test_eq_missing_component(): """ Specifying a condition involving a non-existent component raises an error. """ # No Co or Cr in this database ; Co condition specification should cause failure equilibrium(ALNIFCC4SL_DBF, ['AL', 'NI', 'VA'], ['LIQUID'], {v.T: 1523, v.X('AL'): 0.88811111111111107, v.X('CO'): 0.11188888888888888, v.P: 101325})
def test_eq_charge_alfeo(): """Phases with charged species are correctly calculated and charged balanced in equilibrium""" alfeo = ALFEO_DBF comps = ['AL', 'FE', 'O', 'VA'] phases = list(alfeo.phases.keys()) conds = {v.P: 101325, v.N: 1, v.T: 500, v.X('FE'): 0.2, v.X('O'): 0.6} res = equilibrium(alfeo, comps, phases, conds, verbose=True) # Values below checked with Thermo-Calc assert np.allclose(res.NP.values.squeeze()[:2], [0.47826862, 0.52173138]) assert np.allclose(res.GM.values.flat[0], -257462.33)
def test_eq_ternary_edge_case_mass(): """ Equilibrium along an edge of composition space will still balance mass. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], {v.T: 1523, v.X('AL'): 0.88811111111111107, v.X('CO'): 0.11188888888888888, v.P: 101325}, verbose=True) mass_error = np.nansum(np.squeeze(eq.NP * eq.X), axis=-2) - \ [0.88811111111111107, 0.11188888888888888, 0] assert np.all(np.abs(mass_error) < 0.01)
def test_eq_ternary_edge_misc_gap(): """ Equilibrium at edge of miscibility gap will still balance mass. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], {v.T: 1523, v.X('AL'): 0.33366666666666667, v.X('CO'): 0.44455555555555554, v.P: 101325}, verbose=True) mass_error = np.nansum(np.squeeze(eq.NP * eq.X), axis=-2) - \ [0.33366666666666667, 0.44455555555555554, 0.22177777777777785] assert np.all(np.abs(mass_error) < 0.001)
def test_eq_ternary_inside_mass(): """ Equilibrium in interior of composition space will still balance mass. """ # This test cannot be checked in TC due to a lack of significant figures in the composition eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], {v.T: 1523, v.X('AL'): 0.44455555555555554, v.X('CO'): 0.22277777777777777, v.P: 101325}, verbose=True) assert_allclose(eq.GM.values, -105871.20, atol=0.1) assert_allclose(eq.MU.values.flatten(), [-104655.532294, -142591.644379, -82905.085459], atol=0.1)
def test_eqplot_ternary(): """ eqplot should return an axes object that has a traingular projection when two independent components and one independent potential are passed. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['LIQUID'], {v.T: 2500, v.X('AL'): (0,0.5,0.33), v.X('CO'): (0,0.5,0.3), v.P: 101325}) ax = eqplot(eq) assert isinstance(ax, Axes) assert ax.name == 'triangular'
def test_eq_issue43_chempots_tricky_potentials(): """ Ternary equilibrium with difficult convergence for chemical potentials (gh-43). """ eq = equilibrium(ISSUE43_DBF, ['AL', 'NI', 'CR', 'VA'], ['FCC_A1', 'GAMMA_PRIME'], {v.X('AL'): .1246, v.X('CR'): 0.6, v.T: 1273, v.P: 101325}, verbose=True) chempots = np.array([-135620.9960449, -47269.29002414, -92304.23688281]) assert_allclose(eq.GM.values, -70680.53695) assert_allclose(np.squeeze(eq.MU.values), chempots)
def test_eq_ternary_inside_mass(): """ Equilibrium in interior of composition space will still balance mass. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], {v.T: 1523, v.X('AL'): 0.44455555555555554, v.X('CO'): 0.22277777777777777, v.P: 101325}, verbose=True) assert_allclose(eq.GM.values, -105871.54, atol=0.1) # Thermo-Calc: -105871.54 # Thermo-Calc: [-104653.83, -142595.49, -82905.784] assert_allclose(eq.MU.values.flatten(), [-104653.83, -142595.49, -82905.784], atol=0.1)
def test_eq_issue43_chempots_misc_gap(): """ Equilibrium for complex ternary miscibility gap (gh-43). """ eq = equilibrium(ISSUE43_DBF, ['AL', 'NI', 'CR', 'VA'], 'GAMMA_PRIME', {v.X('AL'): .1246, v.X('CR'): 1e-9, v.T: 1273, v.P: 101325}, verbose=True) chempots = np.array([-206144.57, -272150.79, -64253.652]) assert_allclose(np.nansum(np.squeeze(eq.NP * eq.X), axis=-2), [0.1246, 1e-9, 1-(.1246+1e-9)], rtol=3e-5) assert_allclose(np.squeeze(eq.MU.values), chempots, rtol=1e-5) assert_allclose(np.squeeze(eq.GM.values), -81933.259)
def test_eq_issue43_chempots_misc_gap(): """ Equilibrium for complex ternary miscibility gap (gh-43). """ eq = equilibrium(ISSUE43_DBF, ['AL', 'NI', 'CR', 'VA'], 'GAMMA_PRIME', {v.X('AL'): .1246, v.X('CR'): 1e-9, v.T: 1273, v.P: 101325}, verbose=True) chempots = 8.31451 * np.squeeze(eq['T'].values) * np.array([-19.47631644, -25.71249032, -6.0706158]) mass_error = np.nansum(np.squeeze(eq.NP * eq.X), axis=-2) - \ [0.1246, 1e-9, 1-(.1246+1e-9)] assert np.max(np.fabs(mass_error)) < 1e-9 assert_allclose(np.squeeze(eq.GM.values), -81933.259) assert_allclose(np.squeeze(eq.MU.values), chempots, atol=1)
def test_eq_ternary_edge_case_mass(): """ Equilibrium along an edge of composition space will still balance mass. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], {v.T: 1523, v.X('AL'): 0.8881111111, v.X('CO'): 0.1118888888, v.P: 101325}, verbose=True) mass_error = np.nansum(np.squeeze(eq.NP * eq.X), axis=-2) - \ [0.8881111111, 0.1118888888, 1e-10] assert_allclose(eq.GM.values, -97913.542) # from Thermo-Calc 2017b result_chempots = eq.MU.values.flatten() assert_allclose(result_chempots[:2], [-86994.575, -184582.17], atol=0.1) # from Thermo-Calc 2017b assert result_chempots[2] < -300000 # Estimated assert np.all(np.abs(mass_error) < 1.5e-10)
def test_eq_ternary_inside_mass(): """ Equilibrium in interior of composition space will still balance mass. """ eq = equilibrium(ALCOCRNI_DBF, ['AL', 'CO', 'CR', 'VA'], ['L12_FCC', 'BCC_B2', 'LIQUID'], { v.T: 1523, v.X('AL'): 0.44455555555555554, v.X('CO'): 0.22277777777777777, v.P: 101325 }, verbose=True) mass_error = np.nansum(np.squeeze(eq.NP * eq.X), axis=-2) - \ [0.44455555555555554, 0.22277777777777777, 0.333] assert np.all(np.abs(mass_error) < 0.01)
def test_eq_issue62_last_component_not_va(): """ VA is not last when components are sorted alphabetically. """ test_tdb = """ ELEMENT VA VACUUM 0.0000E+00 0.0000E+00 0.0000E+00! ELEMENT AL FCC_A1 2.6982E+01 4.5773E+03 2.8322E+01! ELEMENT CO HCP_A3 5.8933E+01 4.7656E+03 3.0040E+00! ELEMENT CR BCC_A2 5.1996E+01 4.0500E+03 2.3560E+01! ELEMENT W BCC_A2 1.8385E+02 4.9700E+03 3.2620E+01! PHASE FCC_A1 % 2 1 1 ! CONSTITUENT FCC_A1 :AL,CO,CR,W : VA% : ! """ equilibrium(Database(test_tdb), ['AL', 'CO', 'CR', 'W', 'VA'], ['FCC_A1'], {"T": 1248, "P": 101325, v.X("AL"): 0.081, v.X("CR"): 0.020, v.X("W"): 0.094})
def test_eq_b2_without_all_comps(): """ All-vacancy endmembers are correctly excluded from the computation when fewer than all components in a Database are selected for the calculation. """ equilibrium(Database(ALNIPT_TDB), ['AL', 'NI', 'VA'], 'BCC_B2', {v.X('NI'): 0.4, v.P: 101325, v.T: 1200}, verbose=True)
def test_eq_model_phase_name(): """ Phase name is set in PhaseRecord when using Model-based JIT compilation. """ eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'LIQUID', {v.X('FE'): 0.3, v.T: 1000, v.P: 101325}, model=Model) assert eq.Phase.sel(vertex=0).isel(T=0, P=0, X_FE=0) == 'LIQUID'
def test_equlibrium_no_opt_solver(): """Passing in a solver with `ignore_convergence = True` gives a result.""" class NoOptSolver(InteriorPointSolver): ignore_convergence = True comps = ['PB', 'SN', 'VA'] phases = list(PBSN_DBF.phases.keys()) conds = {v.T: 300, v.P: 101325, v.X('SN'): 0.50} ipopt_solver_eq_res = equilibrium(PBSN_DBF, comps, phases, conds, solver=InteriorPointSolver(), verbose=True) no_opt_eq_res = equilibrium(PBSN_DBF, comps, phases, conds, solver=NoOptSolver(), verbose=True) ipopt_GM = ipopt_solver_eq_res.GM.values.squeeze() no_opt_GM = no_opt_eq_res.GM.values.squeeze() no_opt_MU = no_opt_eq_res.MU.values.squeeze() assert ipopt_GM != no_opt_GM # global min energy is different from lower convex hull assert np.allclose([-17452.5115967], no_opt_GM) # energy from lower convex hull assert np.allclose([-19540.6522632, -15364.3709302], no_opt_MU) # chempots from lower convex hull
def test_equilibrium_solidification_result_properties(): """Test that SolidificationResult objects produced by equilibrium have the required properties.""" # Required properties are that the shape of the output arrays are matching # NOTE: final phase amounts are not tested because they are not guaranteed # to be 0.0 or 1.0 in the same way as in the Scheil simulations. 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 end_temperature = 650 sol_res = simulate_equilibrium_solidification( dbf, comps, phases, initial_composition, start_temperature=start_temperature, end_temperature=end_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()])
def plot_phase_diagram(dbf, trace, lnprob, datasets, temperatures=(300, 2500, 10), fname="phase_diagram.png"): # enable making an initial plot if (trace is not None) and (lnprob is not None): opt_parameters = optimal_parameters_dict(dbf, trace, lnprob) else: opt_parameters = dict() comps = [sp.name for sp in dbf.species] non_va_comps = sorted(set(comps) - {"VA"}) phases = list(dbf.phases.keys()) ax = multiplot( dbf, comps, phases, { v.P: 101325, v.T: temperatures, v.X(non_va_comps[-1]): (0, 1, 0.05) }, datasets, eq_kwargs={"parameters": opt_parameters}, ) fig = ax.figure ax.set_xlim(0, 1) ax.set_ylim(*(temperatures[:2])) fig.savefig(fname) return fig
def test_eq_on_endmember(): """ When the composition condition is right on top of an end-member the convex hull is still correctly constructed (gh-28). """ equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], ['LIQUID', 'B2_BCC'], {v.X('AL'): [0.4, 0.5, 0.6], v.T: [300, 600], v.P: 101325}, verbose=True)
def test_adding_compsets_to_zpf_boundary_sets(): """Test that new composition sets can be added to ZPFBoundarySets successfully.""" compsets_298 = CompsetPair([ BinaryCompset('P1', 298.15, 'B', 0.5, [0.5, 0.5]), BinaryCompset('P2', 298.15, 'B', 0.8, [0.2, 0.8]), ]) compsets_300 = CompsetPair([ BinaryCompset('P1', 300, 'B', 0.5, [0.5, 0.5]), BinaryCompset('P2', 300, 'B', 0.8, [0.2, 0.8]), ]) compsets_300_diff_phases = CompsetPair([ BinaryCompset('P2', 300, 'B', 0.5, [0.5, 0.5]), BinaryCompset('P3', 300, 'B', 0.8, [0.2, 0.8]), ]) zpfbs = ZPFBoundarySets(['A', 'B'], v.X('B')) assert zpfbs.components == ['A', 'B'] assert len(zpfbs.two_phase_regions) == 0 assert len(zpfbs.all_compsets) == 0 zpfbs.add_compsets(compsets_298) assert len(zpfbs.all_compsets) == 1 assert len(zpfbs.two_phase_regions) == 1 zpfbs.add_compsets(compsets_300) # same region, different temperature assert len(zpfbs.all_compsets) == 2 assert len(zpfbs.two_phase_regions) == 1 zpfbs.add_compsets( compsets_300_diff_phases) # new region, different phases assert len(zpfbs.all_compsets) == 3 assert len(zpfbs.two_phase_regions) == 2
def test_eq_output_property(): """ Extra properties can be specified to `equilibrium`. """ equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], ['LIQUID', 'B2_BCC'], {v.X('AL'): 0.25, v.T: (300, 2000, 500), v.P: 101325}, output=['heat_capacity', 'degree_of_ordering'])
def multi_phase_fit(dbf, comps, phases, datasets, phase_models, parameters=None, scheduler=None): scheduler = scheduler or dask.local # TODO: support distributed schedulers for multi_phase_fit. # This can be done if the scheduler passed is a distributed.worker_client if scheduler is not dask.local: raise NotImplementedError('Schedulers other than dask.local are not currently supported for multiphase fitting.') desired_data = datasets.search((tinydb.where('output') == 'ZPF') & (tinydb.where('components').test(lambda x: set(x).issubset(comps))) & (tinydb.where('phases').test(lambda x: len(set(phases).intersection(x)) > 0))) def safe_get(itms, idxx): try: return itms[idxx] except IndexError: return None fit_jobs = [] for data in desired_data: payload = data['values'] conditions = data['conditions'] data_comps = list(set(data['components']).union({'VA'})) phase_regions = defaultdict(lambda: list()) # TODO: Fix to only include equilibria listed in 'phases' for idx, p in enumerate(payload): phase_key = tuple(sorted(rp[0] for rp in p)) if len(phase_key) < 2: # Skip single-phase regions for fitting purposes continue # Need to sort 'p' here so we have the sorted ordering used in 'phase_key' # rp[3] optionally contains additional flags, e.g., "disordered", to help the solver comp_dicts = [(dict(zip([v.X(x.upper()) for x in rp[1]], rp[2])), safe_get(rp, 3)) for rp in sorted(p, key=operator.itemgetter(0))] cur_conds = {} for key, value in conditions.items(): value = np.atleast_1d(np.asarray(value)) if len(value) > 1: value = value[idx] cur_conds[getattr(v, key)] = float(value) phase_regions[phase_key].append((cur_conds, comp_dicts)) for region, region_eq in phase_regions.items(): for req in region_eq: # We are now considering a particular tie region current_statevars, comp_dicts = req region_chemical_potentials = \ dask.delayed(estimate_hyperplane)(dbf, data_comps, phases, current_statevars, comp_dicts, phase_models, parameters) # Now perform the equilibrium calculation for the isolated phases and add the result to the error record for current_phase, cond_dict in zip(region, comp_dicts): # TODO: Messy unpacking cond_dict, phase_flag = cond_dict # We are now considering a particular tie vertex for key, val in cond_dict.items(): if val is None: cond_dict[key] = np.nan cond_dict.update(current_statevars) error = dask.delayed(tieline_error)(dbf, data_comps, current_phase, cond_dict, region_chemical_potentials, phase_flag, phase_models, parameters) fit_jobs.append(error) errors = dask.compute(*fit_jobs, get=scheduler.get_sync) return errors
def get_zpf_data(comps, phases, datasets): """ Return the ZPF data used in the calculation of ZPF error Parameters ---------- comps : list List of active component names phases : list List of phases to consider datasets : espei.utils.PickleableTinyDB Datasets that contain single phase data Returns ------- list List of data dictionaries with keys ``weight``, ``data_comps`` and ``phase_regions``. ``data_comps`` are the components for the data in question. ``phase_regions`` are the ZPF phases, state variables and compositions. """ desired_data = datasets.search( (tinydb.where('output') == 'ZPF') & (tinydb.where('components').test(lambda x: set(x).issubset(comps))) & (tinydb.where('phases').test( lambda x: len(set(phases).intersection(x)) > 0))) zpf_data = [] for data in desired_data: payload = data['values'] conditions = data['conditions'] # create a dictionary of each set of phases containing a list of individual points on the tieline # individual tieline points are tuples of (conditions, {composition dictionaries}) phase_regions = defaultdict(lambda: list()) # TODO: Fix to only include equilibria listed in 'phases' for idx, p in enumerate(payload): phase_key = tuple(sorted(rp[0] for rp in p)) if len(phase_key) < 2: # Skip single-phase regions for fitting purposes continue # Need to sort 'p' here so we have the sorted ordering used in 'phase_key' # rp[3] optionally contains additional flags, e.g., "disordered", to help the solver comp_dicts = [(dict(zip([v.X(x.upper()) for x in rp[1]], rp[2])), _safe_index(rp, 3)) for rp in sorted(p, key=operator.itemgetter(0))] cur_conds = {} for key, value in conditions.items(): value = np.atleast_1d(np.asarray(value)) if len(value) > 1: value = value[idx] cur_conds[getattr(v, key)] = float(value) phase_regions[phase_key].append((cur_conds, comp_dicts)) data_dict = { 'weight': data.get('weight', 1.0), 'data_comps': list(set(data['components']).union({'VA'})), 'phase_regions': list(phase_regions.items()), 'dataset_reference': data['reference'] } zpf_data.append(data_dict) return zpf_data
def test_eq_issue43_chempots_misc_gap(): """ Equilibrium for complex ternary miscibility gap (gh-43). """ eq = equilibrium(ISSUE43_DBF, ['AL', 'NI', 'CR', 'VA'], 'GAMMA_PRIME', { v.X('AL'): .1246, v.X('CR'): 1e-9, v.T: 1273, v.P: 101325 }, verbose=True) chempots = 8.31451 * np.squeeze(eq['T'].values) * np.array( [[[[[-19.47631644, -25.71249032, -6.0706158]]]]]) assert_allclose(eq.GM.values, -81933.259) assert_allclose(eq.MU.values, chempots, atol=1)
def test_equilibrium_solidification_result_properties(): """Test that SolidificationResult objects produced by equilibrium have the required properties.""" # Required properties are that the shape of the output arrays are matching # NOTE: final phase amounts are not tested because they are not guaranteed # to be 0.0 or 1.0 in the same way as in the Scheil simulations. dbf = Database(os.path.join(os.path.dirname(__file__), 'alzn_mey.tdb')) comps = ['AL', 'ZN', 'VA'] phases = sorted(dbf.phases.keys()) initial_composition = {v.X('ZN'): 0.3} start_temperature = 850 sol_res = simulate_equilibrium_solidification( dbf, comps, phases, initial_composition, start_temperature=start_temperature, step_temperature=20.0, verbose=True) num_temperatures = len(sol_res.temperatures) 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()]) assert all([ num_temperatures == len(liq_comps) for liq_comps in sol_res.x_liquid.values() ]) assert all([ num_temperatures == len(nphase) for nphase in sol_res.cum_phase_amounts.values() ]) # The final cumulative solid phase amounts is 1.0 assert np.isclose( np.sum([amnts[-1] for amnts in sol_res.cum_phase_amounts.values()]), 1.0) # The final instantaneous phase amounts is not 1.0 (only the amount of new solid phase added assert np.sum([amnts[-1] for amnts in sol_res.phase_amounts.values()]) < 1.0 # Test serialization for ky, vl in sol_res.to_dict().items(): print(ky, vl, type(ky), type(vl)) json.dumps({ky: vl}) json.dumps(sol_res.to_dict()) # Test round tripping to/from dict rnd_trip_sol_res = SolidificationResult.from_dict(sol_res.to_dict()) assert rnd_trip_sol_res.fraction_liquid == sol_res.fraction_liquid assert rnd_trip_sol_res.fraction_solid == sol_res.fraction_solid assert rnd_trip_sol_res.x_liquid == sol_res.x_liquid assert rnd_trip_sol_res.cum_phase_amounts == sol_res.cum_phase_amounts assert rnd_trip_sol_res.phase_amounts == sol_res.phase_amounts assert rnd_trip_sol_res.temperatures == sol_res.temperatures assert rnd_trip_sol_res.converged == sol_res.converged assert rnd_trip_sol_res.method == sol_res.method
def test_eq_binary(): "Binary phase diagram point equilibrium calculation with magnetism." my_phases = ['LIQUID', 'FCC_A1', 'HCP_A3', 'AL5FE2', 'AL2FE', 'AL13FE4', 'AL5FE4'] comps = ['AL', 'FE', 'VA'] conds = {v.T: 1400, v.P: 101325, v.X('AL'): 0.55} eqx = equilibrium(ALFE_DBF, comps, my_phases, conds, verbose=True) assert_allclose(eqx.GM.values.flat[0], -9.608807e4)
def test_eq_avoid_phase_cycling(): """ Converge without getting stuck in an add/remove phase cycle. """ # This set of conditions is known to trigger the issue my_phases_alfe = ['LIQUID', 'B2_BCC', 'FCC_A1', 'HCP_A3', 'AL5FE2', 'AL2FE', 'AL13FE4', 'AL5FE4'] equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], my_phases_alfe, {v.X('AL'): 0.44, v.T: 1600, v.P: 101325}, verbose=True)