def test_equilibrium_raises_when_no_phases_can_be_active(): """Equliibrium raises when the components passed cannot give any active phases""" # all phases contain AL and/or FE in a sublattice, so no phases can be active equilibrium(ALFE_DBF, ['VA'], list(ALFE_DBF.phases.keys()), { v.T: 300, v.P: 101325 })
def test_equilibrium_raises_with_no_active_phases_passed(): """Passing inactive phases to equilibrium raises a ConditionError.""" # the only phases passed are the disordered phases, which are inactive equilibrium(ALNIFCC4SL_DBF, ['AL', 'NI', 'VA'], ['FCC_A1', 'BCC_A2'], { v.T: 300, v.P: 101325 })
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_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}, pbar=False)
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}, pbar=False)
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, pbar=False)
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_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'], pbar=False)
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 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 time_equilibrium_al_fe(self): equilibrium(self.db_alni, ['AL', 'NI', 'VA'], ['LIQUID', 'FCC_L12'], { v.X('AL'): 0.10, v.T: (300, 2500, 20), v.P: 101325 }, pbar=False)
def time_equilibrium_al_ni(self): equilibrium(self.db_alfe, ['AL', 'FE', 'VA'], ['LIQUID', 'B2_BCC'], { v.X('AL'): 0.25, v.T: (300, 2000, 50), v.P: 101325 }, pbar=False)
def test_eq_unary_issue78(): "Unary equilibrium calculations work with property calculations." eq = equilibrium(ALFE_DBF, ['AL', 'VA'], 'FCC_A1', {v.T: 1200, v.P: 101325}, output='SM') np.testing.assert_allclose(eq.SM, 68.143273) eq = equilibrium(ALFE_DBF, ['AL', 'VA'], 'FCC_A1', {v.T: 1200, v.P: 101325}, output='SM', parameters={'GHSERAL': 1000}) np.testing.assert_allclose(eq.GM, 1000) np.testing.assert_allclose(eq.SM, 0)
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_underdetermined_comps(): """ The number of composition conditions should yield exactly one dependent component. This is the underdetermined case. """ with pytest.raises(ValueError): equilibrium(ALFE_DBF, ['AL', 'FE'], 'LIQUID', {v.T: 2000, v.P: 101325})
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_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)
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}, pbar=False)
def test_unused_equilibrium_kwarg_warns(): "Check that an unused keyword argument raises a warning" with warnings.catch_warnings(record=True) as w: equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'FCC_A1', {v.T: 1300, v.P: 101325, v.X('AL'): 0}, unused_kwarg='should raise a warning') assert len(w) >= 1 categories = [warning.__dict__['_category_name'] for warning in w] assert 'UserWarning' in categories expected_string_fragment = 'keyword arguments were passed, but unused' assert any([expected_string_fragment in str(warning.message) for warning in w])
def test_dilute_condition(): """ 'Zero' and dilute composition conditions are correctly handled. """ eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'FCC_A1', {v.T: 1300, v.P: 101325, v.X('AL'): 0}, verbose=True) assert_allclose(np.squeeze(eq.GM.values), -64415.84, atol=0.1) eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'FCC_A1', {v.T: 1300, v.P: 101325, v.X('AL'): 1e-12}, verbose=True) assert_allclose(np.squeeze(eq.GM.values), -64415.841) assert_allclose(np.squeeze(eq.MU.values), [-385499.682936, -64415.837878], atol=1.0)
def test_eq_some_phases_filtered(): """ Phases are filtered out from equilibrium() when some cannot be built. """ # should not raise; AL13FE4 should be filtered out equilibrium(ALFE_DBF, ['AL', 'VA'], ['FCC_A1', 'AL13FE4'], { v.T: 1200, v.P: 101325 })
def test_dilute_condition(): """ 'Zero' and dilute composition conditions are correctly handled. """ eq = equilibrium(ALFE_DBF, ["AL", "FE", "VA"], "FCC_A1", {v.T: 1300, v.P: 101325, v.X("AL"): 0}, pbar=False) assert_allclose(np.squeeze(eq.GM.values), -64415.84, atol=0.1) eq = equilibrium(ALFE_DBF, ["AL", "FE", "VA"], "FCC_A1", {v.T: 1300, v.P: 101325, v.X("AL"): 1e-8}, pbar=False) assert_allclose(np.squeeze(eq.GM.values), -64415.84069827) assert_allclose(eq.MU.values, [[[[-335723.04320981, -64415.8379852]]]], atol=0.1)
def test_equilibrium_raises_with_invalid_solver(): """ SolverBase instances passed to equilibrium should raise an error. """ equilibrium(CUO_DBF, ['O'], 'GAS', { v.T: 1000, v.P: 1e5 }, solver=SolverBase())
def test_dilute_condition(): """ 'Zero' and dilute composition conditions are correctly handled. """ eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'FCC_A1', {v.T: 1300, v.P: 101325, v.X('AL'): 0}, verbose=True) assert_allclose(np.squeeze(eq.GM.values), -64415.84, atol=0.1) eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'FCC_A1', {v.T: 1300, v.P: 101325, v.X('AL'): 1e-8}, verbose=True) # Checked in TC assert_allclose(np.squeeze(eq.GM.values), -64415.841) # We loosen the tolerance a bit here because our convergence tolerance is too low for the last digit assert_allclose(np.squeeze(eq.MU.values), [-335723.28, -64415.838], atol=1.0)
def test_eq_underdetermined_comps(): """ The number of composition conditions should yield exactly one dependent component. This is the underdetermined case. """ equilibrium(ALFE_DBF, ['AL', 'FE'], 'LIQUID', { v.T: 2000, v.P: 101325 }, pbar=False)
def test_missing_models_with_phase_records_passed_to_equilibrium_raises(): "equilibrium should raise an error if all the active phases are not included in the phase_records" my_phases = ['LIQUID', 'FCC_A1', 'HCP_A3', 'AL5FE2', 'AL2FE', 'AL13FE4', 'AL5FE4'] comps = ['AL', 'FE', 'VA'] conds = {v.T: 1400, v.P: 101325, v.N: 1.0, v.X('AL'): 0.55} models = instantiate_models(ALFE_DBF, comps, my_phases) phase_records = build_phase_records(ALFE_DBF, comps, my_phases, conds, models) with pytest.raises(ValueError): # model=models NOT passed equilibrium(ALFE_DBF, comps, my_phases, conds, verbose=True, phase_records=phase_records)
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"], pbar=False, )
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}, pbar=False, )
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}, pbar=False, )
def test_eq_gas_phase(): eq = equilibrium(CUO_DBF, ['O'], 'GAS', { v.T: 1000, v.P: 1e5 }, verbose=True) np.testing.assert_allclose(eq.GM, -110380.61071, atol=0.1) eq = equilibrium(CUO_DBF, ['O'], 'GAS', { v.T: 1000, v.P: 1e9 }, verbose=True) np.testing.assert_allclose(eq.GM, -7.20909E+04, atol=0.1)
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 plot_convex_hull(dbf, comps, phases, conds, ax=None): if ax is None: ax = plt.gca() result = equilibrium(dbf, comps, phases, conds) unique_phase_sets = np.unique(result['Phase'].values.squeeze(), axis=0) comp_cond = [c for c in conds.keys() if isinstance(c, v.X)] if len(comp_cond) != 1: raise ValueError( f"Exactly one composition condition required for plotting convex hull. Got {len(comp_cond)} with conditions for {comp_cond}" ) comp_cond = str(comp_cond[0]) for phase_set in unique_phase_sets: label = '+'.join([ph for ph in phase_set if ph != '']) # composition indices with the same unique phase unique_phase_idx = np.nonzero( np.all(result['Phase'].values.squeeze() == phase_set, axis=1))[0] masked_result = result.isel(**{comp_cond: unique_phase_idx}) ax.plot(masked_result[comp_cond].squeeze(), masked_result.GM.squeeze(), color='limegreen', linestyle=':', lw=4, label='convex hull', zorder=3) return ax
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_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_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, pbar=False) assert_allclose(eqx.GM.values.flat[0], -9.608807e4)
def estimate_hyperplane(dbf, comps, phases, current_statevars, comp_dicts, phase_models, parameters): region_chemical_potentials = [] parameters = OrderedDict(sorted(parameters.items(), key=str)) for cond_dict, phase_flag in comp_dicts: # 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) if np.any(np.isnan(list(cond_dict.values()))): # This composition is unknown -- it doesn't contribute to hyperplane estimation pass else: # Extract chemical potential hyperplane from multi-phase calculation # Note that we consider all phases in the system, not just ones in this tie region multi_eqdata = equilibrium(dbf, comps, phases, cond_dict, verbose=False, model=phase_models, scheduler=dask.local.get_sync, parameters=parameters) # Does there exist only a single phase in the result with zero internal degrees of freedom? # We should exclude those chemical potentials from the average because they are meaningless. num_phases = len(np.squeeze(multi_eqdata['Phase'].values != '')) zero_dof = np.all((multi_eqdata['Y'].values == 1.) | np.isnan(multi_eqdata['Y'].values)) if (num_phases == 1) and zero_dof: region_chemical_potentials.append(np.full_like(np.squeeze(multi_eqdata['MU'].values), np.nan)) else: region_chemical_potentials.append(np.squeeze(multi_eqdata['MU'].values)) region_chemical_potentials = np.nanmean(region_chemical_potentials, axis=0, dtype=np.float) return region_chemical_potentials
def test_eq_parameter_override(): """ Check that overriding parameters works in equilibrium(). """ comps = ["AL"] dbf = AL_PARAMETER_DBF phases = ['FCC_A1'] conds = {v.P: 101325, v.T: 500} # Check that current database should work as expected eq_res = equilibrium(dbf, comps, phases, conds) np.testing.assert_allclose(eq_res.GM.values.squeeze(), 5000.0) # Check that overriding parameters works eq_res = equilibrium(dbf, comps, phases, conds, parameters={'VV0000': 10000}) np.testing.assert_allclose(eq_res.GM.values.squeeze(), 10000.0)
def ternplot(dbf, comps, phases, conds, x=None, y=None, eq_kwargs=None, **plot_kwargs): """ Calculate the ternary isothermal, isobaric phase diagram. This function is a convenience wrapper around equilibrium() and eqplot(). Parameters ---------- dbf : Database Thermodynamic database containing the relevant parameters. comps : list Names of components to consider in the calculation. phases : list Names of phases to consider in the calculation. conds : dict Maps StateVariables to values and/or iterables of values. For ternplot only one changing composition and one potential coordinate each is supported. x : Composition instance of a pycalphad.variables.composition to plot on the x-axis. Must correspond to an independent condition. y : Composition instance of a pycalphad.variables.composition to plot on the y-axis. Must correspond to an independent condition. eq_kwargs : optional Keyword arguments to equilibrium(). plot_kwargs : optional Keyword arguments to eqplot(). Returns ------- A phase diagram as a figure. Examples -------- None yet. """ eq_kwargs = eq_kwargs if eq_kwargs is not None else dict() indep_comps = [ key for key, value in conds.items() if isinstance(key, v.Composition) and len(np.atleast_1d(value)) > 1 ] indep_pots = [ key for key, value in conds.items() if ((key == v.T) or (key == v.P)) and len(np.atleast_1d(value)) > 1 ] if (len(indep_comps) != 2) or (len(indep_pots) != 0): raise ValueError( 'ternplot() requires exactly two composition coordinates') full_eq = equilibrium(dbf, comps, phases, conds, **eq_kwargs) # TODO: handle x and y as strings with #87 x = x if x in indep_comps else indep_comps[0] y = y if y in indep_comps else indep_comps[1] return eqplot(full_eq, x=x, y=y, **plot_kwargs)
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, pbar=False) assert_allclose(eqx.GM.values.flat[0], -9.608807e4)
def test_equilibrium_result_dataset_can_serialize_to_netcdf(): """ The xarray Dataset returned by equilibrium should serializable to a netcdf file. """ fname = 'eq_result_netcdf_test.nc' eq = equilibrium(ALFE_DBF, ['AL', 'VA'], 'FCC_A1', {v.T: 1200, v.P: 101325}) eq.to_netcdf(fname) os.remove(fname) # cleanup
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_degree_of_ordering(): "Degree of ordering should be calculated properly." my_phases = ['B2_BCC'] comps = ['AL', 'FE', 'VA'] conds = {v.T: 300, v.P: 101325, v.X('AL'): 0.25} eqx = equilibrium(ALFE_DBF, comps, my_phases, conds, output='degree_of_ordering', verbose=True) print('Degree of ordering: {}'.format(eqx.degree_of_ordering.sel(vertex=0).values.flatten())) assert np.isclose(eqx.degree_of_ordering.sel(vertex=0).values.flatten(), np.array([0.66663873]))
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, pbar=False) assert_allclose(eqx.GM.values.flat[0], -5.8351e3, atol=0.1)
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, pbar=False) assert_allclose(eqx.GM.values.flat[0], -5.8351e3)
def test_eq_single_phase(): "Equilibrium energy should be the same as for a single phase with no miscibility gaps." res = calculate(ALFE_DBF, ['AL', 'FE'], 'LIQUID', T=[1400, 2500], P=101325, points={'LIQUID': [[0.1, 0.9], [0.2, 0.8], [0.3, 0.7], [0.7, 0.3], [0.8, 0.2]]}) eq = equilibrium(ALFE_DBF, ['AL', 'FE'], 'LIQUID', {v.T: [1400, 2500], v.P: 101325, v.X('AL'): [0.1, 0.2, 0.3, 0.7, 0.8]}, verbose=True, pbar=False) assert_allclose(eq.GM, res.GM, atol=0.1)
def test_eq_illcond_magnetic_hessian(): """ Check equilibrium of a system with an ill-conditioned Hessian due to magnetism (Tc->0). This is difficult to reproduce so we only include some known examples here. """ # This set of conditions is known to trigger the issue eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], ['FCC_A1', 'AL13FE4'], {v.X('AL'): 0.8, v.T: 300, v.P: 1e5}, pbar=False) assert_allclose(eq.GM.values, [[[-31414.46677]]])
def test_eq_four_sublattice(): """ Balancing mass in a multi-sublattice phase in a single-phase configuration. """ eq = equilibrium(ALNIFCC4SL_DBF, ['AL', 'NI', 'VA'], 'FCC_L12', {v.T: 1073, v.X('NI'): 0.7601, v.P: 101325}, pbar=False) assert_allclose(np.squeeze(eq.X.sel(vertex=0).values), [1-.7601, .7601]) # Not a strict equality here because we can't yet reach TC's value of -87260.6 assert eq.GM.values < -87256.3
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}, pbar=False) 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_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_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}, pbar=False) 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. """ 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}, pbar=False) 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_eqplot_binary(): """ eqplot should return an axes object when one independent component and one independent potential are passed. """ my_phases = ['LIQUID', 'FCC_A1', 'HCP_A3', 'AL5FE2', 'AL2FE', 'AL13FE4', 'AL5FE4'] comps = ['AL', 'FE', 'VA'] conds = {v.T: (1400, 1500, 50), v.P: 101325, v.X('AL'): (0, 1, 0.5)} eq = equilibrium(ALFE_DBF, comps, my_phases, conds) ax = eqplot(eq) assert isinstance(ax, Axes)
def test_eq_composition_cond_sorting(): """ Composition conditions are correctly constructed when the dependent component does not come last in alphabetical order (gh-21). """ eq = equilibrium(ALFE_DBF, ["AL", "FE"], "LIQUID", {v.T: 2000, v.P: 101325, v.X("FE"): 0.2}, pbar=False) # Values computed by Thermo-Calc tc_energy = -143913.3 tc_mu_fe = -184306.01 tc_mu_al = -133815.12 assert_allclose(eq.GM.values, tc_energy) assert_allclose(eq.MU.values, [[[[tc_mu_al, tc_mu_fe]]]], rtol=1e-6)
def test_eq_illcond_hessian(): """ Check equilibrium of a system with an ill-conditioned Hessian. This is difficult to reproduce so we only include some known examples here (gh-23). """ # This set of conditions is known to trigger the issue eq = equilibrium(ALFE_DBF, ['AL', 'FE', 'VA'], 'LIQUID', {v.X('FE'): 0.73999999999999999, v.T: 401.5625, v.P: 1e5}, pbar=False) assert_allclose(eq.GM.values, [[[-16507.22325998]]]) # chemical potentials were checked in TC and accurate to 1 J/mol # pycalphad values used for more significant figures # once again, py33 converges to a slightly different value versus every other python assert_allclose(eq.MU.values, [[[[-55611.954141, -2767.72322]]]], atol=0.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.88811111111111107, v.X("CO"): 0.11188888888888888, v.P: 101325}, pbar=False, ) 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_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}, pbar=False, 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_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"): 0.1246, v.X("CR"): 1e-9, v.T: 1273, v.P: 101325}, verbose=False, pbar=False, ) 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_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"): 0.1246, v.X("CR"): 0.6, v.T: 1273, v.P: 101325}, verbose=False, pbar=False, ) chempots = 8.31451 * np.squeeze(eq["T"].values) * np.array([[[[[-12.78777939, -4.42862046, -8.77499585]]]]]) assert_allclose(eq.GM.values, -70567.7329) assert_allclose(eq.MU.values, chempots, atol=1)
def test_eq_illcond_magnetic_hessian(): """ Check equilibrium of a system with an ill-conditioned Hessian due to magnetism (Tc->0). This is difficult to reproduce so we only include some known examples here. """ # This set of conditions is known to trigger the issue eq = equilibrium( ALFE_DBF, ["AL", "FE", "VA"], ["FCC_A1", "AL13FE4"], {v.X("AL"): 0.8, v.T: 300, v.P: 1e5}, pbar=False, verbose=True, ) assert_allclose(eq.GM.values, [[[-31414.46677]]]) assert_allclose(eq.MU.values, [[[[-8490.140, -123111.773]]]], atol=0.1)
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}, pbar=False, ) 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)