Source code for defermi.chempots.reservoirs

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Collection of chemical potential sets
"""

import warnings
import json
import os.path as op
from pandas import DataFrame
from monty.json import MSONable, MontyEncoder
import copy

from pymatgen.analysis.phase_diagram import PhaseDiagram

from .core import Chempots
from .phase_diagram import PDHandler
from ..tools.utils import format_composition


[docs] class Reservoirs(MSONable): def __init__(self,res_dict,phase_diagram=None,mu_refs=None,are_chempots_delta=False): """ Class to handle dictionaries of chemical potentials. Works almost completely like a python dictionary. Parameters ---------- res_dict : dict Dictionary with reservoir names as key and dictionaries of chemical potentials as values. phase_diagram : PhaseDiagram PhaseDiagram object, useful to convert absolute chempots in referenced chempots. mu_refs : dict Dictionary with chemical potentials of reference elements ({Element:chempot}). If the PhaseDiagram is provided mu_refs is taken from the mu_refs attribute. are_chempots_delta : bool Set this variable to True if chempots in dictionary are referenced values. """ self.res_dict = res_dict self.pd = phase_diagram if mu_refs: self.mu_refs = mu_refs elif self.pd: self.mu_refs = PDHandler(self.pd).get_chempots_reference() else: self.mu_refs = mu_refs warnings.warn('Neither PhaseDiagram or reference chempots have been provided, conversions btw ref and abs value will not be possible',UserWarning) self._are_chempots_delta = are_chempots_delta def __str__(self): df = self.get_dataframe() return df.__str__() def __repr__(self): return self.__str__() def _repr_html_(self): return self.get_dataframe()._repr_html_() def __len__(self): return len(self.res_dict) def __iter__(self): return self.res_dict.keys().__iter__() def __getitem__(self,reskey): return self.res_dict[reskey] def __setitem__(self,reskey,chempots): self.res_dict[reskey] = chempots return def __eq__(self, other): if isinstance(other, dict): return self.res_dict == other elif isinstance(other, Reservoirs): return self.res_dict == other.res_dict else: return False
[docs] def keys(self): return self.res_dict.keys()
[docs] def values(self): return self.res_dict.values()
[docs] def items(self): return self.res_dict.items()
[docs] def copy(self): return Reservoirs(copy.deepcopy(self.res_dict),phase_diagram=self.pd, are_chempots_delta=self.are_chempots_delta,mu_refs=self.mu_refs)
[docs] def update(self, other): if isinstance(other, dict): for key, value in other.items(): self.res_dict[key] = value else: for key, value in other: self.res_dict[key] = value
@property def are_chempots_delta(self): return self._are_chempots_delta
[docs] def as_dict(self): """ Json-serializable dict representation of a Reservoirs object. Returns ------- d : dict Json-serializable dict of a Reservoirs object. """ d = {} d['@module'] = self.__class__.__module__ d['@class'] = self.__class__.__name__ d['res_dict'] = {r:mu.as_dict() for r,mu in self.res_dict.items()} d['phase_diagram'] = self.pd.as_dict() if self.pd else None d['mu_refs'] = self.mu_refs.as_dict() if self.mu_refs else None d['are_chempots_delta'] = self.are_chempots_delta return d
[docs] def to_json(self,path,cls=MontyEncoder): """ Save Reservoirs object as json string or file Parameters ---------- path : str Path to the destination file. If None a string is exported. cls : cls Encoder class for json.dump. The default is MontyEncoder. Returns ------- d : str If path is not set a string is returned. """ d = self.as_dict() if path: with open(path,'w') as file: json.dump(d,file,cls=cls) return else: return d.__str__()
[docs] @classmethod def from_dict(cls,d): """ Constructor of Reservoirs object from dictionary representation. Parameters ---------- d : dict Json-serializable dict of a Reservoirs object. Returns ------- reservoirs : Reservoirs Reservoirs object. """ res_dict = {} for res,chempots in d['res_dict'].items(): res_dict[res] = Chempots.from_dict(chempots) phase_diagram = PhaseDiagram.from_dict(d['phase_diagram']) if d['phase_diagram'] else None mu_refs = Chempots.from_dict(d['mu_refs']) if d['mu_refs'] else None are_chempots_delta = d['are_chempots_delta'] return cls(res_dict,phase_diagram,mu_refs,are_chempots_delta)
[docs] @staticmethod def from_json(path_or_string): """ Build Reservoirs object from json file or string. Parameters ---------- path_or_string : str If an existing path to a file is given the object is constructed reading the json file. Otherwise it will be read as a string. Returns ------- Reservoir object. """ if op.isfile(path_or_string): with open(path_or_string) as file: d = json.load(file) else: d = json.loads(path_or_string) return Reservoirs.from_dict(d)
[docs] def filter_reservoirs(self,inplace=False,elements=None): """ Get new Reservoir object filtering the chempots dictionary. Parameters ---------- inplace : bool Apply changes to current Reservoirs object. elements : list List of element symbols. Returns ------- res : Reservoirs Reservoirs object. """ res = self.copy() mu_refs = self.mu_refs.copy() filtered_dict = res.res_dict if elements: d = filtered_dict.copy() for r in list(d): for el in list(d[r]): if el not in elements: del filtered_dict[r][el] if el in mu_refs.keys(): del mu_refs[el] if inplace: self.res_dict = filtered_dict self.mu_refs = mu_refs return else: res.mu_refs = mu_refs return res
[docs] def get_absolute_res_dict(self): """ Return values of chempots from referenced to absolute """ if self.are_chempots_delta: return {r:mu.get_absolute(self.mu_refs) for r,mu in self.res_dict.items()} else: raise ValueError('Chemical potential values are already absolute')
[docs] def get_referenced_res_dict(self): """ Return values of chempots from absolute to referenced """ if self.are_chempots_delta: raise ValueError('Chemical potential values are already with respect to reference') else: return {r:mu.get_referenced(self.mu_refs) for r,mu in self.res_dict.items()}
[docs] def get_dataframe(self,format_symbols=False,format_compositions=False,all_math=False,ndecimals=None): """ Get DataFrame object of the dictionary of reservoirs. Parameters ---------- format_symbols : bool Format labels of element chempots in latex math format. format_compositions : bool Get Latex format of compositions. all_math : bool Get all characters in composition written in latex's math format. ndecimals : int Number of decimals to round the chemical potentials, if None the numbers are not changed. Returns ------- df : DataFrame DataFrame object. """ res = self._get_res_dict_with_symbols(format_symbols) df = DataFrame(res) df = df.transpose() if format_compositions: new_index = [] for string in df.index: new_string = format_composition(string,all_math=all_math) new_index.append(new_string) df.index = new_index if ndecimals: df = df.round(decimals=ndecimals) return df
[docs] def get_latex_table(self,ndecimals=1): """ Get string with formatted latex table of chemical potentials. """ df = self.get_dataframe(format_symbols=True,ndecimals=ndecimals) table = df.to_latex(escape=False) return table
[docs] def get_plot(self,elements,size=1,**kwargs): """ Plot the stability diagram with the reservoir points. Parameters ---------- elements : list List of strings with element symbols on the diagram axis. size : float Size of the points. The default is 1. **kwargs : dict Kwargs for the add_reservoirs function. Returns ------- Matplotlib object. """ from defermi.chempots.phase_diagram import StabilityDiagram # import here to avoid circular import res = self.copy() if not res.pd: raise ValueError('PhaseDiagram object needs to be stored to plot the stability diagram') plt = PDHandler(res.pd).get_stability_diagram(elements) if not res.are_chempots_delta: res.set_to_referenced() plt = StabilityDiagram(res.pd,size).add_reservoirs(res,elements,**kwargs) return plt
[docs] def set_to_absolute(self): """ Set reservoir dictionary to absolute values of chempots. """ new_res_dict = self.get_absolute_res_dict() self.res_dict = new_res_dict self._are_chempots_delta = False return
[docs] def set_to_referenced(self): """ Set reservoir dictionary to referenced values of chempots. """ new_res_dict = self.get_referenced_res_dict() self.res_dict = new_res_dict self._are_chempots_delta = True return
def _get_res_dict_with_symbols(self,format_symbols=False): """ format_labels (bool): Format labels of element chempots in latex math format. """ new_dict = {} for res,chempots in self.res_dict.items(): new_dict[res] = {} chempots = chempots.mu #keep just dict for DataFrame for el in chempots: if format_symbols: if self.are_chempots_delta: label = '$\\Delta \\mu_{\\text{%s}}$' %el else: label = '$\\mu_{\\text{%s}}$' %el else: label = el new_dict[res][label] = chempots[el] return new_dict
[docs] class PressureReservoirs(Reservoirs): """ Subclass of Reservoirs which contains temperature information. Useful for partial pressure analysis. """ def __init__(self,res_dict,temperature=None,phase_diagram=None,mu_refs=None,are_chempots_delta=False): super().__init__(res_dict,phase_diagram,mu_refs,are_chempots_delta) self.temperature = temperature self.pressures = list(self.res_dict.keys()) def __eq__(self, other): if isinstance(other, dict): return self.res_dict == other elif isinstance(other, PressureReservoirs): return self.res_dict == other.res_dict else: return False
[docs] def as_dict(self): """ Json-serializable dict representation of a PressureReservoirs object. The Pymatgen element is expressed with the symbol of the element. Returns ------- d : dict Json-serializable dict of a PressureReservoirs object. """ d = {} d['@module'] = self.__class__.__module__ d['@class'] = self.__class__.__name__ d['res_dict'] = {r:mu.as_dict() for r,mu in self.res_dict.items()} d['temperature'] = self.temperature d['phase_diagram'] = self.pd.as_dict() if self.pd else None d['mu_refs'] = self.mu_refs.as_dict() if self.mu_refs else None d['are_chempots_delta'] = self.are_chempots_delta return d
[docs] @classmethod def from_dict(cls,d): """ Constructor of PressureReservoirs object from dictionary representation. Parameters ---------- d : dict Returns ------- PressureReservoirs object. """ res_dict = {float(r):Chempots.from_dict(mu) for r,mu in d['res_dict'].items()} temperature = d['temperature'] if 'temperature' in d.keys() else None phase_diagram = PhaseDiagram.from_dict(d['phase_diagram']) if d['phase_diagram'] else None mu_refs = Chempots.from_dict(d['mu_refs']) if d['mu_refs'] else None are_chempots_delta = d['are_chempots_delta'] return cls(res_dict,temperature,phase_diagram,mu_refs,are_chempots_delta)
[docs] @staticmethod def from_json(path_or_string): """ Build PressureReservoirs object from json file or string. Parameters ---------- path_or_string : str If an existing path to a file is given the object is constructed reading the json file. Otherwise it will be read as a string. Returns ------- PressureReservoir object. """ if op.isfile(path_or_string): with open(path_or_string) as file: d = json.load(file) else: d = json.load(path_or_string) return PressureReservoirs.from_dict(d)