Source code for pymatgen.analysis.cost

# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.

"""
This module is used to estimate the cost of various compounds. Costs are taken
from the a CostDB instance, for example a CSV file via CostDBCSV.
For compounds with no cost listed, a Phase Diagram style convex hull
optimization is performed to determine a set of compositions that can be mixed
to give the desired compound with lowest total cost.
"""

import abc
from collections import defaultdict
import csv
import os
import itertools
from monty.design_patterns import singleton
from monty.string import unicode2str

import scipy.constants as const

from pymatgen import Composition, Element
from pymatgen.util.provenance import is_valid_bibtex
from pymatgen.analysis.phase_diagram import PDEntry, PhaseDiagram
from io import open

__author__ = 'Anubhav Jain'
__copyright__ = 'Copyright 2013, The Materials Project'
__version__ = '0.1'
__maintainer__ = 'Anubhav Jain'
__email__ = 'ajain@lbl.gov'
__date__ = 'Aug 27, 2013'

module_dir = os.path.dirname(os.path.abspath(__file__))


[docs]class CostEntry(PDEntry): """ Extends PDEntry to include a BibTeX reference and include language about cost """ def __init__(self, composition, cost, name, reference): """ Args: composition: Composition as a pymatgen.core.structure.Composition cost: Cost (per mol, NOT per kg) of the full Composition name: Optional parameter to name the entry. Defaults to the reduced chemical formula as in PDEntry. reference: Reference data as BiBTeX string """ super().__init__(composition, cost, name) if reference and not is_valid_bibtex(reference): raise ValueError( "Invalid format for cost reference! Should be BibTeX string.") self.reference = reference def __repr__(self): return "CostEntry : {} with cost = {:.4f}".format(self.composition, self.energy)
[docs]class CostDB(metaclass=abc.ABCMeta): """ Abstract class for representing a Cost database. Can be extended, e.g. for file-based or REST-based databases """
[docs] @abc.abstractmethod def get_entries(self, chemsys): """ For a given chemical system, return an array of CostEntries Args: chemsys: array of Elements defining the chemical system. Returns: array of CostEntries """ return
[docs]class CostDBCSV(CostDB): """ Read a CSV file to get costs Format is formula,cost_per_kg,name,BibTeX """ def __init__(self, filename): """ Args: filename (str): Filename of cost database. """ # read in data from file self._chemsys_entries = defaultdict(list) filename = os.path.join(os.path.dirname(__file__), filename) with open(filename, "rt") as f: reader = csv.reader(f, quotechar=unicode2str("|")) for row in reader: comp = Composition(row[0]) cost_per_mol = float(row[1]) * comp.weight.to("kg") * const.N_A pde = CostEntry(comp.formula, cost_per_mol, row[2], row[3]) chemsys = "-".join(sorted([el.symbol for el in pde.composition.elements])) self._chemsys_entries[chemsys].append(pde)
[docs] def get_entries(self, chemsys): """ For a given chemical system, return an array of CostEntries Args: chemsys: array of Elements defining the chemical system. Returns: array of CostEntries """ chemsys = "-".join(sorted([el.symbol for el in chemsys])) return self._chemsys_entries[chemsys]
@singleton class CostDBElements(CostDBCSV): """ Singleton object that provides the cost data for elements """ def __init__(self): """ Init """ CostDBCSV.__init__( self, os.path.join(module_dir, "costdb_elements.csv"))
[docs]class CostAnalyzer: """ Given a CostDB, figures out the minimum cost solutions via convex hull """ def __init__(self, costdb): """ Args: costdb (): Cost database. """ self.costdb = costdb
[docs] def get_lowest_decomposition(self, composition): """ Get the decomposition leading to lowest cost Args: composition: Composition as a pymatgen.core.structure.Composition Returns: Decomposition as a dict of {Entry: amount} """ entries_list = [] elements = [e.symbol for e in composition.elements] for i in range(len(elements)): for combi in itertools.combinations(elements, i + 1): chemsys = [Element(e) for e in combi] x = self.costdb.get_entries(chemsys) entries_list.extend(x) try: pd = PhaseDiagram(entries_list) return pd.get_decomposition(composition) except IndexError: raise ValueError("Error during PD building; most likely, " "cost data does not exist!")
[docs] def get_cost_per_mol(self, comp): """ Get best estimate of minimum cost/mol based on known data Args: comp: Composition as a pymatgen.core.structure.Composition Returns: float of cost/mol """ comp = comp if isinstance(comp, Composition) else Composition(comp) decomp = self.get_lowest_decomposition(comp) return sum(k.energy_per_atom * v * comp.num_atoms for k, v in decomp.items())
[docs] def get_cost_per_kg(self, comp): """ Get best estimate of minimum cost/kg based on known data Args: comp: Composition as a pymatgen.core.structure.Composition Returns: float of cost/kg """ comp = comp if isinstance(comp, Composition) else Composition(comp) return self.get_cost_per_mol(comp) / ( comp.weight.to("kg") * const.N_A)