Beispiel #1
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)
Beispiel #2
0
class SimplexTest(unittest.TestCase):
    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]])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.5]),
                                       [0.5, 0, 0.5])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.75]),
                                       [0.5, 0.25, 0.25])
        np.testing.assert_almost_equal(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)
class SimplexTest(unittest.TestCase):

    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]])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.5]), [0.5, 0, 0.5])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.75]), [0.5, 0.25, 0.25])
        np.testing.assert_almost_equal(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)
Beispiel #4
0
class SimplexTest(unittest.TestCase):
    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 xrange(10):
            coord = np.random.random_sample(size=(3)) / 3
            self.assertTrue(self.simplex.in_simplex(coord))
 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)
Beispiel #6
0
class SimplexTest(unittest.TestCase):

    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 xrange(10):
            coord = np.random.random_sample(size=(3)) / 3
            self.assertTrue(self.simplex.in_simplex(coord))
Beispiel #7
0
    def _in_facet(self, facet, comp):
        """
        Checks if a composition is in a facet.

        Args:
            facet: facet to test.
            comp: Composition to test.
        """
        els = self._pd.elements
        dim = len(els)
        if dim > 1:
            coords = self._pd.qhull_data[facet, :-1]
            simplex = Simplex(coords)
            comp_point = [comp.get_atomic_fraction(e) for e in els[1:]]
            return simplex.in_simplex(comp_point, PDAnalyzer.numerical_tol)
        else:
            return True
Beispiel #8
0
    def _in_facet(self, facet, comp):
        """
        Checks if a composition is in a facet.

        Args:
            facet: facet to test.
            comp: Composition to test.
        """
        els = self._pd.elements
        dim = len(els)
        if dim > 1:
            coords = self._pd.qhull_data[facet, :-1]
            simplex = Simplex(coords)
            comp_point = [comp.get_atomic_fraction(e) for e in els[1:]]
            return simplex.in_simplex(comp_point, PDAnalyzer.numerical_tol)
        else:
            return True
Beispiel #9
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
Beispiel #10
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 xrange(len(facet))]
            simplex = Simplex(coords)
            comp_point = [entry.npH, entry.nPhi]
            return simplex.in_simplex(comp_point,
                                      PourbaixAnalyzer.numerical_tol)
        else:
            return True
Beispiel #11
0
    def test_2dtriangle(self):
        s = Simplex([[0, 1], [1, 1], [1, 0]])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.5]),
                                       [0.5, 0, 0.5])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.75]),
                                       [0.5, 0.25, 0.25])
        np.testing.assert_almost_equal(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])
Beispiel #12
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
Beispiel #13
0
    def _in_facet(self, facet, comp):
        """
        Checks if a composition is in a facet.

        Args:
            facet:
                facet to test.
            comp:
                Composition to test.
        """
        dim = len(self._pd.elements)
        if dim > 1:
            coords = [np.array(self._pd.qhull_data[facet[i]][0:dim - 1])
                      for i in xrange(len(facet))]
            simplex = Simplex(coords)
            comp_point = [comp.get_atomic_fraction(self._pd.elements[i])
                          for i in xrange(1, len(self._pd.elements))]
            return simplex.in_simplex(comp_point, PDAnalyzer.numerical_tol)
        else:
            return True
    def test_2dtriangle(self):
        s = Simplex([[0, 1], [1, 1], [1, 0]])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.5]), [0.5, 0, 0.5])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 1]), [0.5, 0.5, 0])
        np.testing.assert_almost_equal(s.bary_coords([0.5, 0.75]), [0.5, 0.25, 0.25])
        np.testing.assert_almost_equal(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])
Beispiel #15
0
    def _in_facet(self, facet, comp):
        """
        Checks if a composition is in a facet.

        Args:
            facet:
                facet to test.
            comp:
                Composition to test.
        """
        els = self._pd.elements
        dim = len(els)
        if dim > 1:
            coords = [
                np.array(self._pd.qhull_data[facet[i]][0:dim - 1])
                for i in xrange(len(facet))
            ]
            simplex = Simplex(coords)
            comp_point = [
                comp.get_atomic_fraction(els[i]) for i in xrange(1, len(els))
            ]
            return simplex.in_simplex(comp_point, PDAnalyzer.numerical_tol)
        else:
            return True
Beispiel #16
0
    def get_chempot_range_map(self, elements):
        """
        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")]

        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: pd.el_refs[el].energy_per_atom for el in elements}
        chempot_ranges = collections.defaultdict(list)
        for ufacet in ConvexHull(all_chempots).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
Beispiel #17
0
 def simplices(self):
     """
     Returns the simplices of the convex hull.
     """
     return [Simplex([self.points[i] for i in v]) for v in self.vertices]
Beispiel #18
0
 def simplices(self):
     """
     Returns the simplices of the triangulation.
     """
     return [Simplex([self.points[i] for i in v]) for v in self.vertices]
Beispiel #19
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)
        el_refs = {}
        for el in elements:
            el_entries = list(
                filter(
                    lambda e: e.composition.is_element and e.composition.
                    elements[0] == el, entries))
            if len(el_entries) == 0:
                raise PhaseDiagramError(
                    "There are no entries associated with terminal {}.".format(
                        el))
            el_refs[el] = min(el_entries, key=lambda e: e.energy_per_atom)

        data = []
        for entry in entries:
            comp = entry.composition
            row = [comp.get_atomic_fraction(el) for el in elements]
            row.append(entry.energy_per_atom)
            data.append(row)
        data = np.array(data)
        self.all_entries_hulldata = data[:, 1:]

        #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)

        #make sure that if there are multiple entries at the same composition
        #within 1e-4 eV/atom of each other, only use the lower energy one.
        #This fixes the precision errors in the convex hull.
        #This is significantly faster than grouping by composition and then
        #taking the lowest energy of each group
        ind = []
        prev_c = []  # compositions within 1e-4 of current entry
        prev_e = []  # energies of those compositions
        for i in np.argsort([e.energy_per_atom for e in entries]):
            if form_e[i] > -self.formation_energy_tol:
                continue
            epa = entries[i].energy_per_atom
            #trim the front of the lists
            while prev_e and epa > 1e-4 + prev_e[0]:
                prev_c.pop(0)
                prev_e.pop(0)
            frac_comp = entries[i].composition.fractional_composition
            if frac_comp not in prev_c:
                ind.append(i)
            prev_e.append(epa)
            prev_c.append(frac_comp)

        #add the elemental references
        ind.extend([entries.index(el) for el in el_refs.values()])

        qhull_entries = [entries[i] for i in ind]
        qhull_data = data[ind][:, 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.simplices = [Simplex(qhull_data[f, :-1]) for f in self.facets]
        self.all_entries = entries
        self.qhull_data = qhull_data
        self.dim = dim
        self.el_refs = el_refs
        self.elements = elements
        self.qhull_entries = qhull_entries