Ejemplo n.º 1
0
def driving_force_to_hyperplane(
        target_hyperplane_chempots: np.ndarray,
        phase_region: PhaseRegion,
        vertex: RegionVertex,
        parameters: np.ndarray,
        approximate_equilibrium: bool = False) -> float:
    """Calculate the integrated driving force between the current hyperplane and target hyperplane.
    """
    species = phase_region.species
    models = phase_region.models
    current_phase = vertex.phase_name
    cond_dict = {**phase_region.potential_conds, **vertex.comp_conds}
    str_statevar_dict = OrderedDict([
        (str(key), cond_dict[key])
        for key in sorted(phase_region.potential_conds.keys(), key=str)
    ])
    phase_points = vertex.points
    phase_records = vertex.phase_records
    update_phase_record_parameters(phase_records, parameters)
    if phase_points is None:
        # We don't have the phase composition here, so we estimate the driving force.
        # Can happen if one of the composition conditions is unknown or if the phase is
        # stoichiometric and the user did not specify a valid phase composition.
        single_eqdata = calculate_(species, [current_phase],
                                   str_statevar_dict,
                                   models,
                                   phase_records,
                                   pdens=50)
        df = np.multiply(target_hyperplane_chempots,
                         single_eqdata.X).sum(axis=-1) - single_eqdata.GM
        driving_force = float(df.max())
    elif vertex.is_disordered:
        # Construct disordered sublattice configuration from composition dict
        # Compute energy
        # Compute residual driving force
        # TODO: Check that it actually makes sense to declare this phase 'disordered'
        num_dof = sum(
            [len(subl) for subl in models[current_phase].constituents])
        desired_sitefracs = np.ones(num_dof, dtype=np.float_)
        dof_idx = 0
        for subl in models[current_phase].constituents:
            dof = sorted(subl, key=str)
            num_subl_dof = len(subl)
            if v.Species("VA") in dof:
                if num_subl_dof == 1:
                    _log.debug(
                        'Cannot predict the site fraction of vacancies in the disordered configuration %s of %s. Returning driving force of zero.',
                        subl, current_phase)
                    return 0
                else:
                    sitefracs_to_add = [1.0]
            else:
                sitefracs_to_add = np.array(
                    [cond_dict.get(v.X(d)) for d in dof], dtype=np.float_)
                # Fix composition of dependent component
                sitefracs_to_add[np.isnan(
                    sitefracs_to_add)] = 1 - np.nansum(sitefracs_to_add)
            desired_sitefracs[dof_idx:dof_idx +
                              num_subl_dof] = sitefracs_to_add
            dof_idx += num_subl_dof
        single_eqdata = calculate_(species, [current_phase],
                                   str_statevar_dict,
                                   models,
                                   phase_records,
                                   points=np.asarray([desired_sitefracs]))
        driving_force = np.multiply(
            target_hyperplane_chempots,
            single_eqdata.X).sum(axis=-1) - single_eqdata.GM
        driving_force = float(np.squeeze(driving_force))
    else:
        # Extract energies from single-phase calculations
        grid = calculate_(species, [current_phase],
                          str_statevar_dict,
                          models,
                          phase_records,
                          points=phase_points,
                          pdens=50,
                          fake_points=True)
        # TODO: consider enabling approximate for this?
        converged, energy = constrained_equilibrium(phase_records, cond_dict,
                                                    grid)
        if not converged:
            _log.debug(
                'Calculation failure: constrained equilibrium not converged for %s, conditions: %s, parameters %s',
                current_phase, cond_dict, parameters)
            return np.inf
        driving_force = float(
            np.dot(target_hyperplane_chempots, vertex.composition) -
            float(energy))
    return driving_force
def calc_prop_differences(
    eqpropdata: EqPropData,
    parameters: np.ndarray,
    approximate_equilibrium: Optional[bool] = False,
) -> Tuple[np.ndarray, np.ndarray]:
    """
    Calculate differences between the expected and calculated values for a property

    Parameters
    ----------
    eqpropdata : EqPropData
        Data corresponding to equilibrium calculations for a single datasets.
    parameters : np.ndarray
        Array of parameters to fit. Must be sorted in the same symbol sorted
        order used to create the PhaseRecords.
    approximate_equilibrium : Optional[bool]
        Whether or not to use an approximate version of equilibrium that does
        not refine the solution and uses ``starting_point`` instead.

    Returns
    -------
    Tuple[np.ndarray, np.ndarray]
        Pair of
        * differences between the calculated property and expected property
        * weights for this dataset

    """
    if approximate_equilibrium:
        _equilibrium = no_op_equilibrium_
    else:
        _equilibrium = equilibrium_

    dbf = eqpropdata.dbf
    species = eqpropdata.species
    phases = eqpropdata.phases
    pot_conds = eqpropdata.potential_conds
    models = eqpropdata.models
    phase_records = eqpropdata.phase_records
    update_phase_record_parameters(phase_records, parameters)
    params_dict = OrderedDict(zip(map(str, eqpropdata.params_keys),
                                  parameters))
    output = eqpropdata.output
    weights = np.array(eqpropdata.weight, dtype=np.float)
    samples = np.array(eqpropdata.samples, dtype=np.float)

    calculated_data = []
    for comp_conds in eqpropdata.composition_conds:
        cond_dict = OrderedDict(**pot_conds, **comp_conds)
        # str_statevar_dict must be sorted, assumes that pot_conds are.
        str_statevar_dict = OrderedDict([(str(key), vals)
                                         for key, vals in pot_conds.items()])
        grid = calculate_(dbf,
                          species,
                          phases,
                          str_statevar_dict,
                          models,
                          phase_records,
                          pdens=500,
                          fake_points=True)
        multi_eqdata = _equilibrium(species, phase_records, cond_dict, grid)
        # TODO: could be kind of slow. Callables (which are cachable) must be built.
        propdata = _eqcalculate(dbf,
                                species,
                                phases,
                                cond_dict,
                                output,
                                data=multi_eqdata,
                                per_phase=False,
                                callables=None,
                                parameters=params_dict,
                                model=models)

        if 'vertex' in propdata.data_vars[output][0]:
            raise ValueError(
                f"Property {output} cannot be used to calculate equilibrium thermochemical error because each phase has a unique value for this property."
            )

        vals = getattr(propdata, output).flatten().tolist()
        calculated_data.extend(vals)

    calculated_data = np.array(calculated_data, dtype=np.float)

    assert calculated_data.shape == samples.shape, f"Calculated data shape {calculated_data.shape} does not match samples shape {samples.shape}"
    assert calculated_data.shape == weights.shape, f"Calculated data shape {calculated_data.shape} does not match weights shape {weights.shape}"
    differences = calculated_data - samples
    logging.debug(
        f'Equilibrium thermochemical error - output: {output} differences: {differences}, weights: {weights}, reference: {eqpropdata.reference}'
    )
    return differences, weights
Ejemplo n.º 3
0
def estimate_hyperplane(phase_region: PhaseRegion,
                        parameters: np.ndarray,
                        approximate_equilibrium: bool = False) -> np.ndarray:
    """
    Calculate the chemical potentials for the target hyperplane, one vertex at a time

    Notes
    -----
    This takes just *one* set of phase equilibria, a phase region, e.g. a dataset point of
    [['FCC_A1', ['CU'], [0.1]], ['LAVES_C15', ['CU'], [0.3]]]
    and calculates the chemical potentials given all the phases possible at the
    given compositions. Then the average chemical potentials of each end point
    are taken as the target hyperplane for the given equilibria.

    """
    if approximate_equilibrium:
        _equilibrium = no_op_equilibrium_
    else:
        _equilibrium = equilibrium_
    target_hyperplane_chempots = []
    target_hyperplane_phases = []
    species = phase_region.species
    phases = phase_region.phases
    models = phase_region.models
    for vertex in phase_region.vertices:
        phase_records = vertex.phase_records
        update_phase_record_parameters(phase_records, parameters)
        cond_dict = {**vertex.comp_conds, **phase_region.potential_conds}
        if vertex.has_missing_comp_cond:
            # 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
            str_statevar_dict = OrderedDict([
                (str(key), cond_dict[key])
                for key in sorted(phase_region.potential_conds.keys(), key=str)
            ])
            grid = calculate_(species,
                              phases,
                              str_statevar_dict,
                              models,
                              phase_records,
                              pdens=50,
                              fake_points=True)
            multi_eqdata = _equilibrium(phase_records, cond_dict, grid)
            target_hyperplane_phases.append(multi_eqdata.Phase.squeeze())
            # 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 = np.sum(multi_eqdata.Phase.squeeze() != '')
            Y_values = multi_eqdata.Y.squeeze()
            no_internal_dof = np.all((np.isclose(Y_values, 1.))
                                     | np.isnan(Y_values))
            MU_values = multi_eqdata.MU.squeeze()
            if (num_phases == 1) and no_internal_dof:
                target_hyperplane_chempots.append(
                    np.full_like(MU_values, np.nan))
            else:
                target_hyperplane_chempots.append(MU_values)
    target_hyperplane_mean_chempots = np.nanmean(target_hyperplane_chempots,
                                                 axis=0,
                                                 dtype=np.float_)
    return target_hyperplane_mean_chempots
Ejemplo n.º 4
0
def driving_force_to_hyperplane(
        target_hyperplane_chempots: np.ndarray,
        comps: Sequence[str],
        phase_region: PhaseRegion,
        vertex_idx: int,
        parameters: np.ndarray,
        approximate_equilibrium: bool = False) -> float:
    """Calculate the integrated driving force between the current hyperplane and target hyperplane.
    """
    if approximate_equilibrium:
        _equilibrium = no_op_equilibrium_
    else:
        _equilibrium = equilibrium_
    dbf = phase_region.dbf
    species = phase_region.species
    phases = phase_region.phases
    models = phase_region.models
    current_phase = phase_region.region_phases[vertex_idx]
    cond_dict = {
        **phase_region.potential_conds,
        **phase_region.comp_conds[vertex_idx]
    }
    str_statevar_dict = OrderedDict([
        (str(key), cond_dict[key])
        for key in sorted(phase_region.potential_conds.keys(), key=str)
    ])
    phase_flag = phase_region.phase_flags[vertex_idx]
    phase_records = phase_region.phase_records[vertex_idx]
    update_phase_record_parameters(phase_records, parameters)
    for key, val in cond_dict.items():
        if val is None:
            cond_dict[key] = np.nan
    if np.any(np.isnan(list(cond_dict.values()))):
        # We don't actually know the phase composition here, so we estimate it
        single_eqdata = calculate_(dbf,
                                   species, [current_phase],
                                   str_statevar_dict,
                                   models,
                                   phase_records,
                                   pdens=500)
        df = np.multiply(target_hyperplane_chempots,
                         single_eqdata.X).sum(axis=-1) - single_eqdata.GM
        driving_force = float(df.max())
    elif phase_flag == 'disordered':
        # Construct disordered sublattice configuration from composition dict
        # Compute energy
        # Compute residual driving force
        # TODO: Check that it actually makes sense to declare this phase 'disordered'
        num_dof = sum([
            len(set(c).intersection(species))
            for c in dbf.phases[current_phase].constituents
        ])
        desired_sitefracs = np.ones(num_dof, dtype=np.float)
        dof_idx = 0
        for c in dbf.phases[current_phase].constituents:
            dof = sorted(set(c).intersection(comps))
            if (len(dof) == 1) and (dof[0] == 'VA'):
                return 0
            # If it's disordered config of BCC_B2 with VA, disordered config is tiny vacancy count
            sitefracs_to_add = np.array([cond_dict.get(v.X(d)) for d in dof],
                                        dtype=np.float)
            # Fix composition of dependent component
            sitefracs_to_add[np.isnan(
                sitefracs_to_add)] = 1 - np.nansum(sitefracs_to_add)
            desired_sitefracs[dof_idx:dof_idx + len(dof)] = sitefracs_to_add
            dof_idx += len(dof)
        single_eqdata = calculate_(dbf,
                                   species, [current_phase],
                                   str_statevar_dict,
                                   models,
                                   phase_records,
                                   pdens=500)
        driving_force = np.multiply(
            target_hyperplane_chempots,
            single_eqdata.X).sum(axis=-1) - single_eqdata.GM
        driving_force = float(np.squeeze(driving_force))
    else:
        # Extract energies from single-phase calculations
        grid = calculate_(dbf,
                          species, [current_phase],
                          str_statevar_dict,
                          models,
                          phase_records,
                          pdens=500,
                          fake_points=True)
        single_eqdata = _equilibrium(species, phase_records, cond_dict, grid)
        if np.all(np.isnan(single_eqdata.NP)):
            logging.debug(
                'Calculation failure: all NaN phases with phases: {}, conditions: {}, parameters {}'
                .format(current_phase, cond_dict, parameters))
            return np.inf
        select_energy = float(single_eqdata.GM)
        region_comps = []
        for comp in [c for c in sorted(comps) if c != 'VA']:
            region_comps.append(cond_dict.get(v.X(comp), np.nan))
        region_comps[region_comps.index(np.nan)] = 1 - np.nansum(region_comps)
        driving_force = np.multiply(target_hyperplane_chempots,
                                    region_comps).sum() - select_energy
        driving_force = float(driving_force)
    return driving_force