Beispiel #1
0
class CompoundPhaseDiagramTest(unittest.TestCase):
    def setUp(self):
        self.entries = EntrySet.from_csv(str(module_dir /
                                             "pdentries_test.csv"))
        self.pd = CompoundPhaseDiagram(
            self.entries,
            [Composition("Li2O"), Composition("Fe2O3")])

    def test_stable_entries(self):
        stable_formulas = [ent.name for ent in self.pd.stable_entries]
        expected_stable = ["Fe2O3", "Li5FeO4", "LiFeO2", "Li2O"]
        for formula in expected_stable:
            self.assertTrue(formula in stable_formulas)

    def test_get_formation_energy(self):
        stable_formation_energies = {
            ent.name: self.pd.get_form_energy(ent)
            for ent in self.pd.stable_entries
        }
        expected_formation_energies = {
            "Li5FeO4": -7.0773284399999739,
            "Fe2O3": 0,
            "LiFeO2": -0.47455929750000081,
            "Li2O": 0,
        }
        for formula, energy in expected_formation_energies.items():
            self.assertAlmostEqual(energy, stable_formation_energies[formula],
                                   7)

    def test_str(self):
        self.assertIsNotNone(str(self.pd))
Beispiel #2
0
    def get_convex_hull_area(self, composition_space):
        """
        Prints out the area or volume of the convex hull defined by the
        organisms in the promotion set.
        """

        # make a phase diagram of just the organisms in the promotion set
        # (on the lower convex hull)
        pdentries = []
        for organism in self.promotion_set:
            pdentries.append(
                PDEntry(organism.composition, organism.total_energy))
        compound_pd = CompoundPhaseDiagram(pdentries,
                                           composition_space.endpoints)

        # get the data for the convex hull
        qhull_data = compound_pd.qhull_data
        # for some reason, the last point is positive, so remove it
        hull_data = np.delete(qhull_data, -1, 0)

        # make a ConvexHull object from the hull data
        try:
            convex_hull = ConvexHull(hull_data)
        except:
            return None
        if len(composition_space.endpoints) == 2:
            return convex_hull.area
        else:
            return convex_hull.volume
Beispiel #3
0
    def compute_pd_values(self, organisms_list, composition_space):
        """
        Constructs a convex hull from the provided organisms and sets the
        organisms' values to their distances from the convex hull.

        Returns the CompoundPhaseDiagram object computed from the organisms
        in organisms_list.

        Args:
            organisms_list: a list of Organisms whose values we need to compute

            composition_space: the CompositionSpace of the search
        """

        # create a PDEntry object for each organism in the list of organisms
        pdentries = {}
        for organism in organisms_list:
            pdentries[organism.id] = PDEntry(organism.composition,
                                             organism.total_energy)

        # put the pdentries in a list
        pdentries_list = []
        for organism_id in pdentries:
            pdentries_list.append(pdentries[organism_id])

        # create a compound phase diagram object from the list of pdentries
        compound_pd = CompoundPhaseDiagram(pdentries_list,
                                           composition_space.endpoints)

        # transform the pdentries and put them in a dictionary, with the
        # organism id's as the keys
        transformed_pdentries = {}
        for org_id in pdentries:
            transformed_pdentries[org_id] = compound_pd.transform_entries(
                [pdentries[org_id]], composition_space.endpoints)[0][0]

        # put the values in a dictionary, with the organism id's as the keys
        values = {}
        for org_id in pdentries:
            values[org_id] = compound_pd.get_e_above_hull(
                transformed_pdentries[org_id])

        # assign values to the organisms
        for organism in organisms_list:
            organism.value = values[organism.id]

        return compound_pd
Beispiel #4
0
    def is_in_composition_space_pd(self, organism, composition_space,
                                   constraints, pool):
        """
        Returns a boolean indicating whether the organism is in the composition
        space. Whether composition space endpoints are allowed is determined by
        the value of constraints.allow_endpoints.

        Args:
            organism: the Organism to check

            composition_space: the CompositionSpace of the search

            constraints: the Constraints of the search

            pool: the Pool
        """

        # cast the endpoints to PDEntries (just make up some energies)
        pdentries = []
        for endpoint in composition_space.endpoints:
            pdentries.append(PDEntry(endpoint, -10))
        pdentries.append(PDEntry(organism.composition, -10))

        # make a CompoundPhaseDiagram and use it to check if the organism
        # is in the composition space from how many entries it returns
        composition_checker = CompoundPhaseDiagram(pdentries,
                                                   composition_space.endpoints)
        if len(
                composition_checker.transform_entries(
                    pdentries, composition_space.endpoints)[0]) == len(
                        composition_space.endpoints):
            print('Organism {} lies outside the composition space '.format(
                organism.id))
            return False

        # check the composition space endpoints if specified
        if not constraints.allow_endpoints and len(pool.to_list()) > 0:
            for endpoint in composition_space.endpoints:
                if endpoint.almost_equals(
                        organism.composition.reduced_composition):
                    print('Organism {} is at a composition space '
                          'endpoint '.format(organism.id))
                    return False
        return True
Beispiel #5
0
    def get_phase_diagram_plot(self):
        """
        Returns a phase diagram plot, as a matplotlib plot object.
        """

        # set the font to Times, rendered with Latex
        plt.rc('font', **{'family': 'serif', 'serif': ['Times']})
        plt.rc('text', usetex=True)

        # parse the composition space endpoints
        endpoints_line = self.lines[0].split()
        endpoints = []
        for word in endpoints_line[::-1]:
            if word == 'endpoints:':
                break
            else:
                endpoints.append(Composition(word))

        if len(endpoints) < 2:
            print('There must be at least 2 endpoint compositions to make a '
                  'phase diagram.')
            quit()

        # parse the compositions and total energies of all the structures
        compositions = []
        total_energies = []
        for i in range(4, len(self.lines)):
            line = self.lines[i].split()
            compositions.append(Composition(line[1]))
            total_energies.append(float(line[2]))

        # make a list of PDEntries
        pdentries = []
        for i in range(len(compositions)):
            pdentries.append(PDEntry(compositions[i], total_energies[i]))

        # make a CompoundPhaseDiagram
        compound_pd = CompoundPhaseDiagram(pdentries, endpoints)

        # make a PhaseDiagramPlotter
        pd_plotter = PDPlotter(compound_pd, show_unstable=50)
        return pd_plotter.get_plot(label_unstable=False)
Beispiel #6
0
    def get_convex_hull_area(self, composition_space):
        """
        Returns the area/volume of the current convex hull.

        Args:
            composition_space: the CompositionSpace of the search
        """

        # check if the initial population contains organisms at all the
        # endpoints of the composition space
        if self.has_endpoints(composition_space) and \
                self.has_non_endpoint(composition_space):

            # compute and print the area or volume of the convex hull
            pdentries = []
            for organism in self.initial_population:
                pdentries.append(
                    PDEntry(organism.composition, organism.total_energy))
            compound_pd = CompoundPhaseDiagram(pdentries,
                                               composition_space.endpoints)

            # get the data for the convex hull
            qhull_data = compound_pd.qhull_data
            # for some reason, the last point is positive, so remove it
            hull_data = np.delete(qhull_data, -1, 0)

            # make a ConvexHull object from the hull data
            # Sometime this fails, saying that only two points were given to
            # construct the convex hull, even though the if statement above
            # checks that there are enough points.
            try:
                convex_hull = ConvexHull(hull_data)
            except:
                return None
            if len(composition_space.endpoints) == 2:
                return convex_hull.area
            else:
                return convex_hull.volume
Beispiel #7
0
 def setUp(self):
     self.entries = EntrySet.from_csv(str(module_dir /
                                          "pdentries_test.csv"))
     self.pd = CompoundPhaseDiagram(
         self.entries,
         [Composition("Li2O"), Composition("Fe2O3")])
Beispiel #8
0
    def compute_composition_vector(self, composition_space):
        """
        Returns the composition vector of the organism, as a numpy array.

        Args:
            composition_space: the CompositionSpace of the search.
        """

        if composition_space.objective_function == 'epa':
            return None
        elif composition_space.objective_function == 'pd':
            # make CompoundPhaseDiagram and PDAnalyzer objects
            pdentries = []
            for endpoint in composition_space.endpoints:
                pdentries.append(PDEntry(endpoint, -10))
            compound_pd = CompoundPhaseDiagram(pdentries,
                                               composition_space.endpoints)

            # transform the organism's composition
            transformed_entry = compound_pd.transform_entries(
                [PDEntry(self.composition, 10)], composition_space.endpoints)

            # get the transformed species and amounts
            if len(transformed_entry[0]) == 0:
                return None
            transformed_list = str(transformed_entry[0][0]).split()
            del transformed_list[0]
            popped = ''
            while popped != 'with':
                popped = transformed_list.pop()

            # separate the dummy species symbols from the amounts
            symbols = []
            amounts = []
            for entry in transformed_list:
                split_entry = entry.split('0+')
                symbols.append(split_entry[0])
                amounts.append(float(split_entry[1]))

            # make a dictionary mapping dummy species to amounts
            dummy_species_amounts = {}
            for i in range(len(symbols)):
                dummy_species_amounts[DummySpecie(symbol=symbols[i])] = \
                    amounts[i]

            # make Composition object with dummy species, get decomposition
            dummy_comp = Composition(dummy_species_amounts)
            decomp = compound_pd.get_decomposition(dummy_comp)

            # get amounts of the decomposition in terms of the (untransformed)
            # composition space endpoints
            formatted_decomp = {}
            for key in decomp:
                key_dict = key.as_dict()
                comp = Composition(key_dict['entry']['composition'])
                formatted_decomp[comp] = decomp[key]

            # make the composition vector
            composition_vector = []
            # because the random organism creator shuffles the endpoints
            composition_space.endpoints.sort()
            for endpoint in composition_space.endpoints:
                if endpoint in formatted_decomp:
                    composition_vector.append(formatted_decomp[endpoint])
                else:
                    composition_vector.append(0.0)
            return np.array(composition_vector)
Beispiel #9
0
    def scale_volume_pd(self, organism, composition_space, pool):
        """
        Returns a boolean indicating whether volume scaling did not fail.

        Args:
            organism: the Organism whose volume to scale

            composition_space: the CompositionSpace of the search

            pool: the Pool

        Description:

            1. Computes the decomposition of the organism - that is, which
                structures on the convex hull (and their relative amounts) that
                the organism would decompose to, based on its composition.

            2. Computes the weighted average volume per atom of the structures
                in the decomposition.

            3. Scales the volume per atom of the organism to the computed
                value.
        """

        # make CompoundPhaseDiagram object
        pdentries = []
        for org in pool.promotion_set:
            pdentries.append(PDEntry(org.composition, org.total_energy))
        compound_pd = CompoundPhaseDiagram(pdentries,
                                           composition_space.endpoints)

        # transform the organism's composition
        transformed_entry = compound_pd.transform_entries(
            [PDEntry(organism.composition, 10)], composition_space.endpoints)

        # get the transformed species and amounts
        transformed_list = str(transformed_entry[0][0]).split()
        del transformed_list[0]
        popped = ''
        while popped != 'with':
            popped = transformed_list.pop()

        # separate the dummy species symbols from the amounts
        symbols = []
        amounts = []
        for entry in transformed_list:
            split_entry = entry.split('0+')
            symbols.append(split_entry[0])
            amounts.append(float(split_entry[1]))

        # make a dictionary mapping dummy species to amounts
        dummy_species_amounts = {}
        for i in range(len(symbols)):
            dummy_species_amounts[DummySpecie(symbol=symbols[i])] = amounts[i]

        # make Composition object with dummy species, get decomposition
        dummy_comp = Composition(dummy_species_amounts)
        decomp = compound_pd.get_decomposition(dummy_comp)

        # get original compositions and amounts from the decomposition
        fractions = []
        comps = []
        for item in decomp:
            fractions.append(decomp[item])
            first_split = str(item).split(',')
            second_split = first_split[0].split()
            while second_split[0] != 'composition':
                del second_split[0]
            del second_split[0]
            # build the composition string
            comp_string = ''
            for symbol in second_split:
                comp_string += str(symbol)
            comps.append(Composition(comp_string))

        # get weighted average volume per atom of the organisms in the
        # decomposition
        vpa_mean = 0
        for i in range(len(comps)):
            for org in pool.promotion_set:
                if (comps[i].reduced_composition).almost_equals(
                        org.composition.reduced_composition):
                    vpa_mean += (org.cell.volume /
                                 len(org.cell.sites)) * fractions[i]

        # compute the new volume and scale to it
        num_atoms = len(organism.cell.sites)
        new_vol = vpa_mean * num_atoms
        # this is to suppress the warnings produced if the
        # scale_lattice method fails
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')
            organism.cell.scale_lattice(new_vol)
            if str(organism.cell.lattice.a) == 'nan' or \
                    organism.cell.lattice.a > 100:
                print('Volume scaling failed on organism {} during '
                      'development '.format(organism.id))
                return False
        return True