Example #1
0
class SimplexTest(PymatgenTest):

    def setUp(self):
        coords = []
        coords.append([0, 0, 0])
        coords.append([0, 1, 0])
        coords.append([0, 0, 1])
        coords.append([1, 0, 0])
        self.simplex = Simplex(coords)

    def test_in_simplex(self):
        self.assertTrue(self.simplex.in_simplex([0.1, 0.1, 0.1]))
        self.assertFalse(self.simplex.in_simplex([0.6, 0.6, 0.6]))
        for i in range(10):
            coord = np.random.random_sample(size=3) / 3
            self.assertTrue(self.simplex.in_simplex(coord))

    def test_2dtriangle(self):
        s = Simplex([[0, 1], [1, 1], [1, 0]])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 0.5]),
                                    [0.5, 0, 0.5])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 0.75]), [0.5, 0.25, 0.25])
        self.assertArrayAlmostEqual(s.bary_coords([0.75, 0.75]), [0.25, 0.5, 0.25])

        s = Simplex([[1, 1], [1, 0]])
        self.assertRaises(ValueError, s.bary_coords, [0.5, 0.5])

    def test_volume(self):
        # Should be value of a right tetrahedron.
        self.assertAlmostEqual(self.simplex.volume, 1/6)
Example #2
0
 def setUp(self):
     coords = []
     coords.append([0, 0, 0])
     coords.append([0, 1, 0])
     coords.append([0, 0, 1])
     coords.append([1, 0, 0])
     self.simplex = Simplex(coords)
Example #3
0
    def test_2dtriangle(self):
        s = Simplex([[0, 1], [1, 1], [1, 0]])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 0.5]),
                                    [0.5, 0, 0.5])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        self.assertArrayAlmostEqual(s.bary_coords([0.5, 0.75]), [0.5, 0.25, 0.25])
        self.assertArrayAlmostEqual(s.bary_coords([0.75, 0.75]), [0.25, 0.5, 0.25])

        s = Simplex([[1, 1], [1, 0]])
        self.assertRaises(ValueError, s.bary_coords, [0.5, 0.5])
Example #4
0
    def _in_facet(self, facet, entry):
        """
        Checks if a Pourbaix Entry is in a facet.

        Args:
            facet: facet to test.
            entry: Pourbaix Entry to test.
        """
        dim = len(self._keys)
        if dim > 1:
            coords = [np.array(self._pd.qhull_data[facet[i]][0:dim - 1])
                      for i in range(len(facet))]
            simplex = Simplex(coords)
            comp_point = [entry.npH, entry.nPhi]
            return simplex.in_simplex(comp_point,
                                      PourbaixAnalyzer.numerical_tol)
        else:
            return True
Example #5
0
    def get_chempot_range_map(self, elements, referenced=True, joggle=True,
                              force_use_pyhull=False):
        """
        Returns a chemical potential range map for each stable entry.

        Args:
            elements: Sequence of elements to be considered as independent
                variables. E.g., if you want to show the stability ranges
                of all Li-Co-O phases wrt to uLi and uO, you will supply
                [Element("Li"), Element("O")]
            referenced: If True, gives the results with a reference being the
                energy of the elemental phase. If False, gives absolute values.
            joggle (boolean): Whether to joggle the input to avoid precision
                errors.
            force_use_pyhull (boolean): Whether the pyhull algorithm is always
                used, even when scipy is present.

        Returns:
            Returns a dict of the form {entry: [simplices]}. The list of
            simplices are the sides of the N-1 dim polytope bounding the
            allowable chemical potential range of each entry.
        """
        all_chempots = []
        pd = self._pd
        facets = pd.facets
        for facet in facets:
            chempots = self.get_facet_chempots(facet)
            all_chempots.append([chempots[el] for el in pd.elements])
        inds = [pd.elements.index(el) for el in elements]
        el_energies = {el: 0.0 for el in elements}
        if referenced:
            el_energies = {el: pd.el_refs[el].energy_per_atom
                           for el in elements}
        chempot_ranges = collections.defaultdict(list)
        vertices = [list(range(len(self._pd.elements)))]
        if len(all_chempots) > len(self._pd.elements):
            vertices = get_facets(all_chempots, joggle=joggle,
                                  force_use_pyhull=force_use_pyhull)
        for ufacet in vertices:
            for combi in itertools.combinations(ufacet, 2):
                data1 = facets[combi[0]]
                data2 = facets[combi[1]]
                common_ent_ind = set(data1).intersection(set(data2))
                if len(common_ent_ind) == len(elements):
                    common_entries = [pd.qhull_entries[i]
                                      for i in common_ent_ind]
                    data = np.array([[all_chempots[i][j]
                                      - el_energies[pd.elements[j]]
                                      for j in inds] for i in combi])
                    sim = Simplex(data)
                    for entry in common_entries:
                        chempot_ranges[entry].append(sim)

        return chempot_ranges
Example #6
0
    def _in_facet(self, facet, entry):
        """
        Checks if a Pourbaix Entry is in a facet.

        Args:
            facet: facet to test.
            entry: Pourbaix Entry to test.
        """
        dim = len(self._keys)
        if dim > 1:
            coords = [
                np.array(self._pd.qhull_data[facet[i]][0:dim - 1])
                for i in range(len(facet))
            ]
            simplex = Simplex(coords)
            comp_point = [entry.npH, entry.nPhi]
            return simplex.in_simplex(comp_point,
                                      PourbaixAnalyzer.numerical_tol)
        else:
            return True
Example #7
0
 def _get_facet(self, comp):
     """
     Get any facet that a composition falls into. Cached so successive
     calls at same composition are fast.
     """
     if set(comp.elements).difference(self._pd.elements):
         raise ValueError('{} has elements not in the phase diagram {}'
                          ''.format(comp, self._pd.elements))
     c = [comp.get_atomic_fraction(e) for e in self._pd.elements[1:]]
     for f, s in zip(self._pd.facets, self._pd.simplices):
         if Simplex(s).in_simplex(c, PDAnalyzer.numerical_tol / 10):
             return f
     raise RuntimeError("No facet found for comp = {}".format(comp))
Example #8
0
    def __init__(self, entries, elements=None):
        """
        Standard constructor for phase diagram.

        Args:
            entries ([PDEntry]): A list of PDEntry-like objects having an
                energy, energy_per_atom and composition.
            elements ([Element]): Optional list of elements in the phase
                diagram. If set to None, the elements are determined from
                the the entries themselves.
        """
        if elements is None:
            elements = set()
            for entry in entries:
                elements.update(entry.composition.elements)
        elements = list(elements)
        dim = len(elements)

        get_reduced_comp = lambda e: e.composition.reduced_composition

        entries = sorted(entries, key=get_reduced_comp)

        el_refs = {}
        min_entries = []
        all_entries = []
        for c, g in itertools.groupby(entries, key=get_reduced_comp):
            g = list(g)
            min_entry = min(g, key=lambda e: e.energy_per_atom)
            if c.is_element:
                el_refs[c.elements[0]] = min_entry
            min_entries.append(min_entry)
            all_entries.extend(g)

        if len(el_refs) != dim:
            raise PhaseDiagramError(
                "There are no entries associated with a terminal element!.")

        data = np.array(
            [[e.composition.get_atomic_fraction(el)
              for el in elements] + [e.energy_per_atom] for e in min_entries])

        # Use only entries with negative formation energy
        vec = [el_refs[el].energy_per_atom for el in elements] + [-1]
        form_e = -np.dot(data, vec)
        inds = np.where(form_e < -self.formation_energy_tol)[0].tolist()

        # Add the elemental references
        inds.extend([min_entries.index(el) for el in el_refs.values()])

        qhull_entries = [min_entries[i] for i in inds]
        qhull_data = data[inds][:, 1:]

        # Add an extra point to enforce full dimensionality.
        # This point will be present in all upper hull facets.
        extra_point = np.zeros(dim) + 1 / dim
        extra_point[-1] = np.max(qhull_data) + 1
        qhull_data = np.concatenate([qhull_data, [extra_point]], axis=0)

        if dim == 1:
            self.facets = [qhull_data.argmin(axis=0)]
        else:
            facets = get_facets(qhull_data)
            finalfacets = []
            for facet in facets:
                # Skip facets that include the extra point
                if max(facet) == len(qhull_data) - 1:
                    continue
                m = qhull_data[facet]
                m[:, -1] = 1
                if abs(np.linalg.det(m)) > 1e-14:
                    finalfacets.append(facet)
            self.facets = finalfacets

        self.simplexes = [Simplex(qhull_data[f, :-1]) for f in self.facets]
        self.all_entries = all_entries
        self.qhull_data = qhull_data
        self.dim = dim
        self.el_refs = el_refs
        self.elements = elements
        self.qhull_entries = qhull_entries
        self._stable_entries = set(self.qhull_entries[i]
                                   for i in set(itertools.chain(*self.facets)))