def test_ternary_symmetric_param(): "Generate the other two ternary parameters if only the zeroth is specified." check_energy(Model(DBF, ['AL', 'CR', 'NI'], 'FCC_A1'), \ {v.T: 300, v.SiteFraction('FCC_A1', 0, 'AL'): 1.97135e-1, v.SiteFraction('FCC_A1', 0, 'CR'): 1.43243e-2, v.SiteFraction('FCC_A1', 0, 'NI'): 7.88541e-1}, -37433.794, mode='sympy')
def test_shift_reference_state_model_contribs_take_effect(): """Shift reference state with contrib_mods set adds contributions to the pure elements.""" TDB = """ ELEMENT A GRAPHITE 12.011 1054.0 5.7423 ! ELEMENT B BCC_A2 55.847 4489.0 27.2797 ! TYPE_DEFINITION % SEQ * ! PHASE TEST % 1 1 ! CONSTITUENT TEST : A,B: ! """ dbf = Database(TDB) comps = ['A', 'B'] phase = 'TEST' m = Model(dbf, comps, phase) refstates = [ReferenceState('A', phase), ReferenceState('B', phase)] m.shift_reference_state(refstates, dbf) statevars = { v.T: 298.15, v.P: 101325, v.SiteFraction(phase, 0, 'A'): 0.5, v.SiteFraction(phase, 0, 'B'): 0.5, } # ideal mixing should be present for GMR idmix_val = 2 * 0.5 * np.log(0.5) * v.R * 298.15 check_output(m, statevars, 'GMR', idmix_val) # shifting the reference state, adding an excess contribution # should see that addition in the output m.shift_reference_state(refstates, dbf, contrib_mods={'xsmix': S(1000.0)}) # each pure element contribution is has xsmix changed from 0 to 1 # At x=0.5, the reference xsmix energy is added to by 0.5*1000.0, which is # then subtracted out of the GM energy check_output(m, statevars, 'GMR', idmix_val - 1000.0)
def test_ternary_rkm_solution(): "Solution phase with ternary interaction parameters." check_energy(Model(DBF, ['AL', 'CR', 'NI'], 'LIQUID'), \ {v.T: 1500, v.SiteFraction('LIQUID', 0, 'AL'): 0.44, v.SiteFraction('LIQUID', 0, 'CR'): 0.20, v.SiteFraction('LIQUID', 0, 'NI'): 0.36}, \ -1.16529e5, mode='sympy')
def moles(self, species): "Number of moles of species or elements." species = v.Species(species) is_pure_element = (len(species.constituents.keys()) == 1 and list(species.constituents.keys())[0] == species.name) result = S.Zero normalization = S.Zero if is_pure_element: element = list(species.constituents.keys())[0] for idx, sublattice in enumerate(self.constituents): active = set(sublattice).intersection(self.components) result += self.site_ratios[idx] * \ sum(int(spec.number_of_atoms > 0) * spec.constituents.get(element, 0) * v.SiteFraction(self.phase_name, idx, spec) for spec in active) normalization += self.site_ratios[idx] * \ sum(spec.number_of_atoms * v.SiteFraction(self.phase_name, idx, spec) for spec in active) else: for idx, sublattice in enumerate(self.constituents): active = set(sublattice).intersection({species}) if len(active) == 0: continue result += self.site_ratios[idx] * sum(v.SiteFraction(self.phase_name, idx, spec) for spec in active) normalization += self.site_ratios[idx] * \ sum(int(spec.number_of_atoms > 0) * v.SiteFraction(self.phase_name, idx, spec) for spec in active) return result / normalization
def test_constituents_not_in_model(): """Test that parameters with constituent arrays not matching the phase model are filtered out correctly""" dbf = Database(TDB_PARAMETER_FILTERS_TEST) modA = Model(dbf, ['A', 'B'], 'ALPHA') modB = Model(dbf, ['B', 'C'], 'BETA') assert v.SiteFraction('ALPHA', 0, 'B') not in modA.ast.free_symbols assert v.SiteFraction('BETA', 1, 'D') not in modB.ast.free_symbols assert v.SiteFraction('BETA', 2, 'C') not in modB.ast.free_symbols
def test_binary_magnetic_ordering(): "Two-component phase with IHJ magnetic model and ordering." # ordered case check_energy(Model(DBF, ['CR', 'NI'], 'L12_FCC'), \ {v.T: 300, v.SiteFraction('L12_FCC', 0, 'CR'): 4.86783e-2, v.SiteFraction('L12_FCC', 0, 'NI'): 9.51322e-1, v.SiteFraction('L12_FCC', 1, 'CR'): 9.33965e-1, v.SiteFraction('L12_FCC', 1, 'NI'): 6.60348e-2}, \ -9.23953e3, mode='sympy')
def test_binary_magnetic(): "Two-component phase with IHJ magnetic model." # disordered case check_energy(Model(DBF, ['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_magnetic_endmember_mixing_energy_is_zero(): """The mixing energy for an endmember with a magnetic contribution should be zero.""" m = Model(CRFE_DBF, ['CR', 'FE', 'VA'], 'BCC_A2') statevars = { v.T: 300, v.SiteFraction('BCC_A2', 0, 'CR'): 0, v.SiteFraction('BCC_A2', 0, 'FE'): 1, v.SiteFraction('BCC_A2', 1, 'VA'): 1 } check_output(m, statevars, 'GM_MIX', 0.0)
def test_negative_site_fraction(): "Raise exception on negative site fraction." check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): -0.3, v.SiteFraction('LIQUID', 0, 'NI'): -2}, \ 5.52773e3, mode='sympy') check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): -0.3, v.SiteFraction('LIQUID', 0, 'NI'): -2}, \ 5.52773e3, mode='numpy')
def test_binary_dilute(): "Dilute binary solution phase." check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 1e-12, v.SiteFraction('LIQUID', 0, 'NI'): 1.0-1e-12}, \ 5.52773e3, mode='sympy') check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 1e-12, v.SiteFraction('LIQUID', 0, 'NI'): 1.0-1e-12}, \ 5.52773e3, mode='numpy')
def test_case_sensitivity(): "Case sensitivity of component and phase names." check_energy(Model(DBF, ['Cr', 'nI'], 'Liquid'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 1e-12, v.SiteFraction('liquid', 0, 'ni'): 1}, \ 5.52773e3, mode='sympy') check_energy(Model(DBF, ['Cr', 'nI'], 'Liquid'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 1e-12, v.SiteFraction('liquid', 0, 'ni'): 1}, \ 5.52773e3, mode='numpy')
def test_zero_site_fraction(): "Energy of a binary solution phase where one site fraction is zero." check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 0, v.SiteFraction('LIQUID', 0, 'NI'): 1}, \ 5.52773e3, mode='sympy') check_energy(Model(DBF, ['CR', 'NI'], 'LIQUID'), \ {v.T: 300, v.SiteFraction('LIQUID', 0, 'CR'): 0, v.SiteFraction('LIQUID', 0, 'NI'): 1}, \ 5.52773e3, mode='numpy')
def test_binary_xiong_twostate_einstein(): "Phase with Xiong magnetic, two-state and Einstein energy contributions." femn_dbf = Database(FEMN_TDB) mod = Model(femn_dbf, ['FE', 'MN', 'VA'], 'LIQUID') check_energy(mod, { v.T: 10, v.SiteFraction('LIQUID', 0, 'FE'): 1, v.SiteFraction('LIQUID', 0, 'MN'): 0, v.SiteFraction('LIQUID', 1, 'VA'): 1 }, 10158.591, mode='sympy') check_energy(mod, { v.T: 300, v.SiteFraction('LIQUID', 0, 'FE'): 0.3, v.SiteFraction('LIQUID', 0, 'MN'): 0.7, v.SiteFraction('LIQUID', 1, 'VA'): 1 }, 4200.8435, mode='sympy') check_energy(mod, { v.T: 1500, v.SiteFraction('LIQUID', 0, 'FE'): 0.8, v.SiteFraction('LIQUID', 0, 'MN'): 0.2, v.SiteFraction('LIQUID', 1, 'VA'): 1 }, -86332.217, mode='sympy')
def test_non_zero_reference_mixing_enthalpy_for_va_interaction(): """The referenced mixing enthalpy for a Model with a VA interaction parameter is non-zero.""" m = Model(VA_INTERACTION_DBF, ['AL', 'VA'], 'FCC_A1') refstates = [ReferenceState('AL', 'FCC_A1')] m.shift_reference_state(refstates, VA_INTERACTION_DBF) statevars_pure = { v.T: 300, v.SiteFraction('FCC_A1', 0, 'AL'): 1, v.SiteFraction('FCC_A1', 0, 'VA'): 0, v.SiteFraction('FCC_A1', 1, 'VA'): 1 } check_output(m, statevars_pure, 'GMR', 0.0) statevars_mix = { v.T: 300, v.SiteFraction('FCC_A1', 0, 'AL'): 0.5, v.SiteFraction('FCC_A1', 0, 'VA'): 0.5, v.SiteFraction('FCC_A1', 1, 'VA'): 1 } # 4000.0 * 0.5=2000 +500 # (Y0VA doesn't contribute), but the VA endmember does (not referenced) check_output(m, statevars_mix, 'HMR', 2500.0) statevars_mix = { v.T: 300, v.SiteFraction('FCC_A1', 0, 'AL'): 0.5, v.SiteFraction('FCC_A1', 0, 'VA'): 0.5, v.SiteFraction('FCC_A1', 1, 'VA'): 1 } # 4000.0 * 0.5 (Y0VA doesn't contribute) check_output(m, statevars_mix, 'HM_MIX', 2000.0)
def test_endmember_mixing_energy_is_zero(): """The mixing energy for an endmember in a multi-sublattice model should be zero.""" m = Model(CUMG_DBF, ['CU', 'MG', 'VA'], 'CU2MG') statevars = { v.T: 300, v.SiteFraction('CU2MG', 0, 'CU'): 1, v.SiteFraction('CU2MG', 0, 'MG'): 0, v.SiteFraction('CU2MG', 1, 'CU'): 0, v.SiteFraction('CU2MG', 1, 'MG'): 1, } check_output(m, statevars, 'GM_MIX', 0.0)
def test_reference_energy_of_unary_twostate_einstein_magnetic_is_zero(): """The referenced energy for the pure elements in a unary Model with twostate and Einstein contributions referenced to that phase is zero.""" m = Model(FEMN_DBF, ['FE', 'VA'], 'LIQUID') statevars = { v.T: 298.15, v.SiteFraction('LIQUID', 0, 'FE'): 1, v.SiteFraction('LIQUID', 1, 'VA'): 1 } refstates = [ReferenceState(v.Species('FE'), 'LIQUID')] m.shift_reference_state(refstates, FEMN_DBF) check_output(m, statevars, 'GMR', 0.0)
def test_get_data_quantities_AL_NI_VA_interaction(): """Test that an interaction with a VA produces the correct data quantities We just have a template database that has the phase defined. We then hot patch the Model object to have the GM from the fixed model we printed out and the data we printed out. The hot patch is needed because this is formation enthalpy data and the model needs to have the lower order terms in composition. One possible issue is that the new GM in the fixed model does not have any individual contributions, so it cannot be used to test excluded model contributions. The only excluded model contributions in this data are idmix, but the property we are testing is HM_FORM, so the feature transform of the idmix property should be zero. """ # Hack the namespace to make the copy-pasted Gibbs energy function work from sympy import log, Piecewise T = v.T data = [{'components': ['AL', 'NI', 'VA'], 'phases': ['BCC_B2'], 'solver': {'mode': 'manual', 'sublattice_occupancies': [[1.0, [0.5, 0.5], 1.0], [1.0, [0.75, 0.25], 1.0]], 'sublattice_site_ratios': [0.5, 0.5, 1.0], 'sublattice_configurations': (('AL', ('NI', 'VA'), 'VA'), ('AL', ('NI', 'VA'), 'VA')), 'comment': 'BCC_B2 sublattice configuration (2SL)'}, 'conditions': {'P': 101325.0, 'T': np.array([300.])}, 'reference_state': 'SGTE91', 'output': 'HM_FORM', 'values': np.array([[[-40316.61077, -56361.58554]]]), 'reference': 'C. Jiang 2009 (constrained SQS)', 'excluded_model_contributions': ['idmix']}, {'components': ['AL', 'NI', 'VA'], 'phases': ['BCC_B2'], 'solver': {'mode': 'manual', 'sublattice_occupancies': [[1.0, [0.5, 0.5], 1.0], [1.0, [0.75, 0.25], 1.0]], 'sublattice_site_ratios': [0.5, 0.5, 1.0], 'sublattice_configurations': (('AL', ('NI', 'VA'), 'VA'), ('AL', ('NI', 'VA'), 'VA')), 'comment': 'BCC_B2 sublattice configuration (2SL)'}, 'conditions': {'P': 101325.0, 'T': np.array([300.])}, 'reference_state': 'SGTE91', 'output': 'HM_FORM', 'values': np.array([[[-41921.43363, -57769.49473]]]), 'reference': 'C. Jiang 2009 (relaxed SQS)', 'excluded_model_contributions': ['idmix']}] NEW_GM = 8.3145*T*(0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "AL")*log(v.SiteFraction("BCC_B2", 0, "AL")), v.SiteFraction("BCC_B2", 0, "AL") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "NI")*log(v.SiteFraction("BCC_B2", 0, "NI")), v.SiteFraction("BCC_B2", 0, "NI") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "VA")*log(v.SiteFraction("BCC_B2", 0, "VA")), v.SiteFraction("BCC_B2", 0, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "AL")*log(v.SiteFraction("BCC_B2", 1, "AL")), v.SiteFraction("BCC_B2", 1, "AL") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "NI")*log(v.SiteFraction("BCC_B2", 1, "NI")), v.SiteFraction("BCC_B2", 1, "NI") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "VA")*log(v.SiteFraction("BCC_B2", 1, "VA")), v.SiteFraction("BCC_B2", 1, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + Piecewise((v.SiteFraction("BCC_B2", 2, "VA")*log(v.SiteFraction("BCC_B2", 2, "VA")), v.SiteFraction("BCC_B2", 2, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI"))) + (45262.9*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA") + 45262.9*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA"))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + (1.0*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*Piecewise((10083 - 4.813*T, (T >= 298.15) & (T < 2900.0)), (0, True)) + v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA")*(9.52839e-8*T**3 + 0.00123463*T**2 + 0.000871898*T*log(T) + 1.31471*T - 64435.3 + 23095.2/T) + v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "VA")*v.SiteFraction("BCC_B2", 2, "VA")*(10.0*T + 16432.5) + v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*(9.52839e-8*T**3 + 0.00123463*T**2 + 0.000871898*T*log(T) + 1.31471*T - 64435.3 + 23095.2/T) + 1.0*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA")*Piecewise((8715.084 - 3.556*T, (T >= 298.15) & (T < 3000.0)), (0, True)) + 32790.6*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "VA")*v.SiteFraction("BCC_B2", 2, "VA") + v.SiteFraction("BCC_B2", 0, "VA")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*(10.0*T + 16432.5) + 32790.6*v.SiteFraction("BCC_B2", 0, "VA")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA"))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) dbf = Database("""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ Date: 2019-12-08 18:05 $ Components: AL, NI, VA $ Phases: BCC_B2 $ Generated by brandon (pycalphad 0.8.1.post1) $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ELEMENT AL FCC_A1 26.982 4577.3 28.322 ! ELEMENT NI FCC_A1 58.69 4787.0 29.796 ! ELEMENT VA VACUUM 0.0 0.0 0.0 ! TYPE_DEFINITION % SEQ * ! DEFINE_SYSTEM_DEFAULT ELEMENT 2 ! DEFAULT_COMMAND DEFINE_SYSTEM_ELEMENT VA ! PHASE BCC_B2 % 3 0.5 0.5 1 ! CONSTITUENT BCC_B2 :AL,NI,VA:AL,NI,VA:VA: ! """) mod = Model(dbf, ['AL', 'NI', 'VA'], 'BCC_B2') dd = {ky: 0.0 for ky in mod.models.keys()} dd['GM'] = NEW_GM mod.models = dd print(mod.HM) config_tup = (('AL',), ('NI', 'VA'), ('VA',)) calculate_dict = get_prop_samples(data, config_tup) sample_condition_dicts = _get_sample_condition_dicts(calculate_dict, list(map(len, config_tup))) qty = get_data_quantities('HM_FORM', mod, [0], data, sample_condition_dicts) print(qty) assert np.all(np.isclose([-6254.7802775, -5126.1206475, -7458.3974225, -6358.04118875], qty))
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='numpy')
def test_order_disorder_mixing_energy_is_nan(): """The endmember-referenced mixing energy is undefined and the energy should be NaN.""" m = Model(ALFE_DBF, ['AL', 'FE', 'VA'], 'B2_BCC') statevars = { v.T: 300, v.SiteFraction('B2_BCC', 0, 'AL'): 1, v.SiteFraction('B2_BCC', 0, 'FE'): 0, v.SiteFraction('B2_BCC', 1, 'AL'): 0, v.SiteFraction('B2_BCC', 1, 'FE'): 1, v.SiteFraction('B2_BCC', 2, 'VA'): 1 } check_output(m, statevars, 'GM_MIX', np.nan)
def build_sitefractions(phase_name, sublattice_configurations, sublattice_occupancies): """Convert nested lists of sublattice configurations and occupancies to a list of dictionaries. The dictionaries map SiteFraction symbols to occupancy values. Note that zero occupancy site fractions will need to be added separately since the total degrees of freedom aren't known in this function. Parameters ---------- phase_name : str Name of the phase sublattice_configurations : [[str]] sublattice configuration sublattice_occupancies : [[float]] occupancy of each sublattice Returns ------- [[float]] a list of site fractions over sublattices """ result = [] for config, occ in zip(sublattice_configurations, sublattice_occupancies): sitefracs = {} config = [[c] if not isinstance(c, (list, tuple)) else c for c in config] occ = [[o] if not isinstance(o, (list, tuple)) else o for o in occ] if len(config) != len(occ): raise ValueError( 'Sublattice configuration length differs from occupancies') for sublattice_idx in range(len(config)): if isinstance(config[sublattice_idx], (list, tuple)) != isinstance(occ[sublattice_idx], (list, tuple)): raise ValueError( 'Sublattice configuration type differs from occupancies') if not isinstance(config[sublattice_idx], (list, tuple)): # This sublattice is fully occupied by one component sitefracs[v.SiteFraction( phase_name, sublattice_idx, config[sublattice_idx])] = occ[sublattice_idx] else: # This sublattice is occupied by multiple elements if len(config[sublattice_idx]) != len(occ[sublattice_idx]): raise ValueError( 'Length mismatch in sublattice configuration') for comp, val in zip(config[sublattice_idx], occ[sublattice_idx]): sitefracs[v.SiteFraction(phase_name, sublattice_idx, comp)] = val result.append(sitefracs) return result
def test_pure_numpy(): "Pure component end-members in numpy mode." check_energy(Model(DBF, ['AL'], 'LIQUID'), \ {v.T: 2000, v.SiteFraction('LIQUID', 0, 'AL'): 1}, \ -1.28565e5, mode='numpy') check_energy(Model(DBF, ['AL'], 'B2'), \ {v.T: 1400, v.SiteFraction('B2', 0, 'AL'): 1, v.SiteFraction('B2', 1, 'AL'): 1}, \ -6.57639e4, mode='numpy') check_energy(Model(DBF, ['AL'], 'L12_FCC'), \ {v.T: 800, v.SiteFraction('L12_FCC', 0, 'AL'): 1, v.SiteFraction('L12_FCC', 1, 'AL'): 1}, \ -3.01732e4, mode='numpy')
def test_degenerate_zero_ordering(): "Degenerate sublattice configuration has zero ordering energy." mod = Model(DBF, ['CR', 'NI'], 'L12_FCC') sub_dict = { 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 } #print({x: mod.models[x].subs(sub_dict) for x in mod.models}) desired = mod.models['ord'].xreplace(sub_dict).evalf() assert abs(desired - 0) < 1e-5, "%r != %r" % (desired, 0)
def test_changing_model_ast_also_changes_mixing_energy(): """If a models contribution is modified, the mixing energy should update accordingly.""" m = Model(CUMG_DBF, ['CU', 'MG', 'VA'], 'CU2MG') m.models['mag'] = 1000 statevars = { v.T: 300, v.SiteFraction('CU2MG', 0, 'CU'): 1, v.SiteFraction('CU2MG', 0, 'MG'): 0, v.SiteFraction('CU2MG', 1, 'CU'): 0, v.SiteFraction('CU2MG', 1, 'MG'): 1, } check_output(m, statevars, 'GM_MIX', 1000) m.endmember_reference_model.models['mag'] = 1000 check_output(m, statevars, 'GM_MIX', 0)
def _translate_endmember_to_array(endmember, variables): site_fractions = sorted(variables, key=str) frac_array = np.zeros(len(site_fractions)) for idx, component in enumerate(endmember): frac_array[site_fractions.index( v.SiteFraction(site_fractions[0].phase_name, idx, component))] = 1 return frac_array
def _map_internal_dof(input_database, components, phase_name, points): """ Map matrix of internal degrees of freedom to global compositions. """ # Map the internal degrees of freedom to global coordinates # Normalize site ratios by the sum of site ratios times a factor # related to the site fraction of vacancies phase_obj = input_database.phases[phase_name] site_ratio_normalization = np.zeros(points.shape[:-1]) phase_compositions = np.empty(points.shape[:-1] + (len(components), )) variables, sublattice_dof = generate_dof(phase_obj, components) for idx, sublattice in enumerate(phase_obj.constituents): vacancy_column = np.ones(points.shape[:-1]) if 'VA' in set(sublattice): var_idx = variables.index(v.SiteFraction(phase_obj.name, idx, 'VA')) vacancy_column -= points[..., :, var_idx] site_ratio_normalization += phase_obj.sublattices[idx] * vacancy_column for col, comp in enumerate(components): avector = [ float(vxx.species == comp) * phase_obj.sublattices[vxx.sublattice_index] for vxx in variables ] phase_compositions[..., :, col] = np.divide(np.dot(points[..., :, :], avector), site_ratio_normalization) return phase_compositions
def ideal_mixing_energy(self, dbe): #pylint: disable=W0613 """ Returns the ideal mixing energy in symbolic form. """ phase = dbe.phases[self.phase_name] # Normalize site ratios site_ratio_normalization = self._site_ratio_normalization(phase) site_ratios = phase.sublattices site_ratios = [c / site_ratio_normalization for c in site_ratios] ideal_mixing_term = S.Zero for subl_index, sublattice in enumerate(phase.constituents): active_comps = set(sublattice).intersection(self.components) if len(active_comps) == 1: continue # no mixing if only one species in sublattice ratio = site_ratios[subl_index] for comp in active_comps: sitefrac = \ v.SiteFraction(phase.name, subl_index, comp) # We lose some precision here, but this makes the limit behave nicely # We're okay until fractions of about 1e-12 (platform-dependent) mixing_term = Piecewise((sitefrac * log(sitefrac), sitefrac > MIN_SITE_FRACTION / 10.), (0, True)) ideal_mixing_term += (mixing_term * ratio) ideal_mixing_term *= (v.R * v.T) return ideal_mixing_term
def __init__(self, dbf, comps, phase, solution=None, kmax=None): self.components = set([x.upper() for x in comps]) if 'VA' in self.components: raise ValueError('Vacancies are unsupported in TestModel') self.models = dict() variables = [ v.SiteFraction(phase.upper(), 0, x) for x in sorted(self.components) ] if solution is None: solution = np.random.dirichlet( np.ones_like(variables, dtype=np.int)) self.solution = dict(list(zip(variables, solution))) kmax = kmax if kmax is not None else 2 scale_factor = 1e4 * len(self.components) ampl_scale = 1e3 * np.ones(kmax, dtype=np.float) freq_scale = 10 * np.ones(kmax, dtype=np.float) polys = Add(*[ ampl_scale[i] * sin(freq_scale[i] * Add(*[ Add(*[(varname - sol)**(j + 1) for varname, sol in self.solution.items()]) for j in range(kmax) ]))**2 for i in range(kmax) ]) self.models['test'] = scale_factor * Add( *[(varname - sol)**2 for varname, sol in self.solution.items()]) + polys
def mole_fraction(species_name, phase_name, constituent_array, site_ratios): """ Return an abstract syntax tree of the mole fraction of the given species as a function of its constituent site fractions. Note that this will treat vacancies the same as any other component, i.e., this will not give the correct _overall_ composition for sublattices containing vacancies with other components by normalizing by a factor of 1 - y_{VA}. This is because we use this routine in the order-disorder model to calculate the disordered site fractions from the ordered site fractions, so we need _all_ site fractions, including VA, to sum to unity. """ # Normalize site ratios site_ratio_normalization = 0 numerator = S.Zero for idx, sublattice in enumerate(constituent_array): # sublattices with only vacancies don't count if len(sublattice) == 1 and sublattice[0] == 'VA': continue if species_name in list(sublattice): site_ratio_normalization += site_ratios[idx] numerator += site_ratios[idx] * \ v.SiteFraction(phase_name, idx, species_name) if site_ratio_normalization == 0 and species_name == 'VA': return 1 if site_ratio_normalization == 0: raise ValueError('Couldn\'t find ' + species_name + ' in ' + \ str(constituent_array)) return numerator / site_ratio_normalization
def test_reference_energy_for_different_phase(): """The referenced energy a different phase should be correct.""" m = Model(ALFE_DBF, ['AL', 'FE', 'VA'], 'AL2FE') # formation reference states refstates = [ ReferenceState('AL', 'FCC_A1'), ReferenceState('FE', 'BCC_A2') ] m.shift_reference_state(refstates, ALFE_DBF) statevars = { v.T: 300, v.SiteFraction('AL2FE', 0, 'AL'): 1, v.SiteFraction('AL2FE', 1, 'FE'): 1 } check_output(m, statevars, 'GMR', -28732.525) # Checked in Thermo-Calc
def degree_of_ordering(self): result = S.Zero site_ratio_normalization = S.Zero # Calculate normalization factor for idx, sublattice in enumerate(self.constituents): active = set(sublattice).intersection(self.components) subl_content = sum(int(spec.number_of_atoms > 0) * v.SiteFraction(self.phase_name, idx, spec) for spec in active) site_ratio_normalization += self.site_ratios[idx] * subl_content site_ratios = [c/site_ratio_normalization for c in self.site_ratios] for comp in self.components: comp_result = S.Zero for idx, sublattice in enumerate(self.constituents): active = set(sublattice).intersection(set(self.components)) if comp in active: comp_result += site_ratios[idx] * Abs(v.SiteFraction(self.phase_name, idx, comp) - self.moles(comp)) / self.moles(comp) result += comp_result return result / sum(int(spec.number_of_atoms > 0) for spec in self.components)