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_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 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_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_magnetic_reference_energy_is_zero(): """The referenced energy binary magnetic Model is zero.""" m = Model(CRFE_DBF, ['CR', 'FE', 'VA'], 'BCC_A2') refstates = [ ReferenceState('CR', 'BCC_A2'), ReferenceState('FE', 'BCC_A2') ] m.shift_reference_state(refstates, CRFE_DBF) statevars_FE = { 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_FE, 'GMR', 0.0) statevars_CR = { v.T: 300, v.SiteFraction('BCC_A2', 0, 'CR'): 1, v.SiteFraction('BCC_A2', 0, 'FE'): 0, v.SiteFraction('BCC_A2', 1, 'VA'): 1 } check_output(m, statevars_CR, 'GMR', 0.0)
def get_thermochemical_data(dbf, comps, phases, datasets, weight_dict=None, symbols_to_fit=None): """ Parameters ---------- dbf : pycalphad.Database Database to consider comps : list List of active component names phases : list List of phases to consider datasets : espei.utils.PickleableTinyDB Datasets that contain single phase data weight_dict : dict Dictionary of weights for each data type, e.g. {'HM': 200, 'SM': 2} symbols_to_fit : list Parameters to fit. Used to build the models and PhaseRecords. Returns ------- list List of data dictionaries to iterate over """ # phase by phase, then property by property, then by model exclusions if weight_dict is None: weight_dict = {} if symbols_to_fit is not None: symbols_to_fit = sorted(symbols_to_fit) else: symbols_to_fit = database_symbols_to_fit(dbf) # estimated from NIST TRC uncertainties property_std_deviation = { 'HM': 500.0 / weight_dict.get('HM', 1.0), # J/mol 'SM': 0.2 / weight_dict.get('SM', 1.0), # J/K-mol 'CPM': 0.2 / weight_dict.get('CPM', 1.0), # J/K-mol } properties = [ 'HM_FORM', 'SM_FORM', 'CPM_FORM', 'HM_MIX', 'SM_MIX', 'CPM_MIX' ] ref_states = [] for el in get_pure_elements(dbf, comps): ref_state = ReferenceState(el, dbf.refstates[el]['phase']) ref_states.append(ref_state) all_data_dicts = [] for phase_name in phases: for prop in properties: desired_data = get_prop_data( comps, phase_name, prop, datasets, additional_query=(where('solver').exists())) if len(desired_data) == 0: continue unique_exclusions = set([ tuple(sorted(d.get('excluded_model_contributions', []))) for d in desired_data ]) for exclusion in unique_exclusions: data_dict = { 'phase_name': phase_name, 'prop': prop, # needs the following keys to be added: # species, calculate_dict, phase_records, model, output, weights } # get all the data with these model exclusions if exclusion == tuple([]): exc_search = ( ~where('excluded_model_contributions').exists()) & ( where('solver').exists()) else: exc_search = (where('excluded_model_contributions').test( lambda x: tuple(sorted(x)) == exclusion)) & ( where('solver').exists()) curr_data = get_prop_data(comps, phase_name, prop, datasets, additional_query=exc_search) calculate_dict = get_prop_samples(dbf, comps, phase_name, curr_data) mod = Model(dbf, comps, phase_name, parameters=symbols_to_fit) if prop.endswith('_FORM'): output = ''.join(prop.split('_')[:-1]) + 'R' mod.shift_reference_state( ref_states, dbf, contrib_mods={e: sympy.S.Zero for e in exclusion}) else: output = prop for contrib in exclusion: mod.models[contrib] = sympy.S.Zero mod.reference_model.models[contrib] = sympy.S.Zero species = sorted(unpack_components(dbf, comps), key=str) data_dict['species'] = species model = {phase_name: mod} statevar_dict = { getattr(v, c, None): vals for c, vals in calculate_dict.items() if isinstance(getattr(v, c, None), v.StateVariable) } statevar_dict = OrderedDict( sorted(statevar_dict.items(), key=lambda x: str(x[0]))) str_statevar_dict = OrderedDict( (str(k), vals) for k, vals in statevar_dict.items()) phase_records = build_phase_records( dbf, species, [phase_name], statevar_dict, model, output=output, parameters={s: 0 for s in symbols_to_fit}, build_gradients=False, build_hessians=False) data_dict['str_statevar_dict'] = str_statevar_dict data_dict['phase_records'] = phase_records data_dict['calculate_dict'] = calculate_dict data_dict['model'] = model data_dict['output'] = output data_dict['weights'] = np.array( property_std_deviation[prop.split('_')[0]]) / np.array( calculate_dict.pop('weights')) all_data_dicts.append(data_dict) return all_data_dicts
def build_eqpropdata( data: tinydb.database.Document, dbf: Database, parameters: Optional[Dict[str, float]] = None, data_weight_dict: Optional[Dict[str, float]] = None) -> EqPropData: """ Build EqPropData for the calculations corresponding to a single dataset. Parameters ---------- data : tinydb.database.Document Document corresponding to a single ESPEI dataset. dbf : Database Database that should be used to construct the `Model` and `PhaseRecord` objects. parameters : Optional[Dict[str, float]] Mapping of parameter symbols to values. data_weight_dict : Optional[Dict[str, float]] Mapping of a data type (e.g. `HM` or `SM`) to a weight. Returns ------- EqPropData """ parameters = parameters if parameters is not None else {} data_weight_dict = data_weight_dict if data_weight_dict is not None else {} property_std_deviation = { 'HM': 500.0, # J/mol 'SM': 0.2, # J/K-mol 'CPM': 0.2, # J/K-mol } params_keys, _ = extract_parameters(parameters) data_comps = list(set(data['components']).union({'VA'})) species = sorted(unpack_components(dbf, data_comps), key=str) data_phases = filter_phases(dbf, species, candidate_phases=data['phases']) models = instantiate_models(dbf, species, data_phases, parameters=parameters) output = data['output'] property_output = output.split('_')[ 0] # property without _FORM, _MIX, etc. samples = np.array(data['values']).flatten() reference = data.get('reference', '') # Models are now modified in response to the data from this data if 'reference_states' in data: property_output = output[:-1] if output.endswith( 'R' ) else output # unreferenced model property so we can tell shift_reference_state what to build. reference_states = [] for el, vals in data['reference_states'].items(): reference_states.append( ReferenceState( v.Species(el), vals['phase'], fixed_statevars=vals.get('fixed_state_variables'))) for mod in models.values(): mod.shift_reference_state(reference_states, dbf, output=(property_output, )) data['conditions'].setdefault( 'N', 1.0 ) # Add default for N. Nothing else is supported in pycalphad anyway. pot_conds = OrderedDict([(getattr(v, key), unpack_condition(data['conditions'][key])) for key in sorted(data['conditions'].keys()) if not key.startswith('X_')]) comp_conds = OrderedDict([(v.X(key[2:]), unpack_condition(data['conditions'][key])) for key in sorted(data['conditions'].keys()) if key.startswith('X_')]) phase_records = build_phase_records(dbf, species, data_phases, { **pot_conds, **comp_conds }, models, parameters=parameters, build_gradients=True, build_hessians=True) # Now we need to unravel the composition conditions # (from Dict[v.X, Sequence[float]] to Sequence[Dict[v.X, float]]), since the # composition conditions are only broadcast against the potentials, not # each other. Each individual composition needs to be computed # independently, since broadcasting over composition cannot be turned off # in pycalphad. rav_comp_conds = [ OrderedDict(zip(comp_conds.keys(), pt_comps)) for pt_comps in zip(*comp_conds.values()) ] # Build weights, should be the same size as the values total_num_calculations = len(rav_comp_conds) * np.prod( [len(vals) for vals in pot_conds.values()]) dataset_weights = np.array(data.get('weight', 1.0)) * np.ones(total_num_calculations) weights = (property_std_deviation.get(property_output, 1.0) / data_weight_dict.get(property_output, 1.0) / dataset_weights).flatten() return EqPropData(dbf, species, data_phases, pot_conds, rav_comp_conds, models, params_keys, phase_records, output, samples, weights, reference)
def test_underspecified_refstate_raises(): """A Model cannot be shifted to a new reference state unless references for all pure elements are specified.""" m = Model(FEMN_DBF, ['FE', 'MN', 'VA'], 'LIQUID') refstates = [ReferenceState(v.Species('FE'), 'LIQUID')] with pytest.raises(DofError): m.shift_reference_state(refstates, FEMN_DBF)