Source code for aiida_catmat.workchains.vasp_catmat

"""`VaspCatMatWorkChain` wraps `VaspMultiStageWorkChain` to perform two ccnsecutive
calculations to calculate cathode properties considering fully intercalated and deintercalated
structures.
"""
from pymatgen.core.periodic_table import Element

from aiida.orm import Dict, StructureData
from aiida.common import AttributeDict
from aiida.engine import calcfunction, WorkChain, ToContext, if_
from aiida.plugins import DataFactory, WorkflowFactory

VaspMultiStageWorkChain = WorkflowFactory('catmat.vasp_multistage')  #pylint: disable=invalid-name
PotcarData = DataFactory('vasp.potcar')  #pylint: disable=invalid-name
KpointsData = DataFactory('array.kpoints')  #pylint: disable=invalid-name
# StructureData = DataFactory('structure')  #pylint: disable=invalid-name


[docs]@calcfunction def update_structure(structure: StructureData, anode: Dict) -> StructureData: """Returns fully deintercalated structure Args: structure (StructureData): Fully intercalated structure anode (Dict): A dictionary with keys to be alkali metal ion strings. Returns: StructureData: Fully deintercalted structure object. """ strc_pmg = structure.get_pymatgen_structure(add_spin=True) species = strc_pmg.species el_to_remove = list(anode.get_dict().keys()) for sp in species: #pylint: disable=invalid-name if sp.element == Element(el_to_remove[0]): strc_pmg.remove_species([sp]) return StructureData(pymatgen_structure=strc_pmg)
[docs]@calcfunction def calculate_cathode_props( #pylint: disable=too-many-locals discharged: Dict, charged: Dict, discharged_structure: StructureData, charged_structure: StructureData, anode: Dict ) -> Dict: """Calculates and returns cathode properties. Args: discharged (Dict): Results dictionary for fully intercalated structure. charged (Dict): Results dictionary for fully deintercalated structure. discharged_structure (StructureData): Structure of fully intercalated structure. charged_structure (StructureData): Structure of fully deintercalated structure. anode (Dict): A dictionary with keys to be alkali metal ion strings and values be the chemical potential. Returns: Dict: Calcualted cathode properties. """ F_CONSTANT = 96485.3 #C/mol #pylint: disable=invalid-name def _get_stg_idx(item): return item.split('_')[1] stgs = list(discharged.keys()) stgs.sort(key=_get_stg_idx) last_stg = stgs[-1] enrg_discharged = discharged.get_dict()[last_stg]['final_energy'] enrg_charged = charged.get_dict()[last_stg]['final_energy'] anode_info = anode.get_dict() anode_el = list(anode_info.keys())[0] anode_mu = list(anode_info.values())[0] nions = discharged_structure.get_composition()[anode_el] discharged_structure_pmg = discharged_structure.get_pymatgen_structure() charged_structure_pmg = charged_structure.get_pymatgen_structure() a_dischg = discharged_structure_pmg.lattice.a b_dischg = discharged_structure_pmg.lattice.b c_dischg = discharged_structure_pmg.lattice.c vol_dischg = discharged_structure_pmg.lattice.volume a_chg = charged_structure_pmg.lattice.a b_chg = charged_structure_pmg.lattice.b c_chg = charged_structure_pmg.lattice.c vol_chg = charged_structure_pmg.lattice.volume a_change = ((a_chg - a_dischg) / a_dischg) * 100 b_change = ((b_chg - b_dischg) / b_dischg) * 100 c_change = ((c_chg - c_dischg) / c_dischg) * 100 vol_change = ((vol_chg - vol_dischg) / vol_dischg) * 100 ocv = -((enrg_discharged - enrg_charged - (anode_mu * nions)) / nions) density_dischg = discharged_structure_pmg.density #g/cm3 mw_dischg = discharged_structure_pmg.composition.weight #g/mol specific_capacity_grav = (nions * F_CONSTANT * 1000) / (mw_dischg * 3600) #mAh/g specific_capacity_vol = ((nions * F_CONSTANT * 1000) / (mw_dischg * 3600)) * density_dischg #mAh/cm3 energy_density_grav = specific_capacity_grav * ocv #Wh/kg energy_density_vol = specific_capacity_vol * ocv #Wh/L ocv_dict = { 'energy_unit': 'eV', 'open_circuit_voltage_unit': 'V', 'lattice_parameters_unit': 'A', 'volume_change_unit': '%', 'change_direction': 'charging', 'battery_type': f'{list(anode_info.keys())[0]}-ion', 'gravimetric_specific_capacity_unit': 'mAh/g', 'volumetric_specific_capacity_unit': 'mAh/cm^3', 'gravimetric_energy_density_unit': 'Wh/kg', 'volumetric_energy_density_unit': 'Wh/L', 'lattice_a_change': round(a_change, 3), 'lattice_b_change': round(b_change, 3), 'lattice_c_change': round(c_change, 3), 'volume_change': round(vol_change, 1), 'open_circuit_voltage': round(ocv, 2), 'gravimetric_specific_capacity': round(specific_capacity_grav, 1), 'volumetric_specific_capacity': round(specific_capacity_vol, 1), 'gravimetric_energy_density': round(energy_density_grav, 1), 'volumetric_energy_density': round(energy_density_vol, 1), 'energy_of_charged_state': enrg_charged, 'energy_of_discharged_state': enrg_discharged, f'number_of_extracted_{list(anode_info.keys())[0]}_ions': nions, 'anode_chemical_potential': anode_mu } return Dict(dict=ocv_dict)
[docs]class VaspCatMatWorkChain(WorkChain): """Convergence WorkChain"""
[docs] @classmethod def define(cls, spec): super().define(spec) # Expose VaspMultiStageWorkChain inputs spec.expose_inputs(VaspMultiStageWorkChain) # Define VaspCatMatWorkChain specific inputs spec.input('anode', valid_type=Dict, required=True, help='Chemical potential of anode') spec.input( 'discharged_calculated_data', valid_type=Dict, required=False, help='Output dictionary of previously calculated structure!' ) spec.input( 'discharged_relaxed_structure', valid_type=StructureData, required=False, help='Relaxed structure of previously calculated structure!' ) # Exit codes # Define outline spec.outline( cls.initialize, if_(cls.should_run_discharged)(cls.run_discharged), cls.run_charged, cls.results, ) # Expose outputs spec.output('cathode_props', valid_type=Dict, required=False, help='output dictionary with OCV results')
[docs] def initialize(self): """Initialize inputs and settings""" # Setup inputs self.ctx.inputs = AttributeDict(self.exposed_inputs(VaspMultiStageWorkChain))
[docs] def should_run_discharged(self): """Checks whether should run calculation on discharged structure""" self.ctx.should_run_discharged = True if 'discharged_calculated_data' in self.inputs: if 'discharged_relaxed_structure' in self.inputs: self.ctx.discharged_calculated_data = self.inputs.discharged_calculated_data self.ctx.discharged_relaxed_structure = self.inputs.discharged_relaxed_structure self.ctx.should_run_discharged = False return self.ctx.should_run_discharged
[docs] def run_discharged(self): """Submit VaspMultiStageWorkChain on discharged structure""" self.ctx.inputs['metadata']['label'] = 'discharged_structured' self.ctx.inputs['metadata']['call_link_label'] = 'run_discharged_structured' running = self.submit(VaspMultiStageWorkChain, **self.ctx.inputs) self.report(f'Submitted VaspMultiStageWorkChain <pk>:{running.pk} for discharged structure!') return ToContext(wc_discharged=running)
[docs] def run_charged(self): """Submit VaspMultiStageWorkChain on charged structure""" # Update structure if self.ctx.should_run_discharged: self.ctx.inputs.structure = update_structure( #pylint: disable=unexpected-keyword-arg self.ctx.wc_discharged.outputs.structure, self.inputs.anode, metadata={ 'label': 'update_structure', 'description': 'calcfunction for deintercalation', 'call_link_label': 'run_update_structure' } ) else: self.ctx.inputs.structure = update_structure( #pylint: disable=unexpected-keyword-arg self.ctx.discharged_relaxed_structure, self.inputs.anode, metadata={ 'label': 'update_structure', 'description': 'calcfunction for deintercalation', 'call_link_label': 'run_update_structure' } ) self.ctx.inputs['metadata']['label'] = 'charged_structured' self.ctx.inputs['metadata']['call_link_label'] = 'run_charged_structured' running = self.submit(VaspMultiStageWorkChain, **self.ctx.inputs) self.report(f'Submitted VaspMultiStageWorkChain <pk>:{running.pk} for charged structure!') return ToContext(wc_charged=running)
[docs] def results(self): """Handle results""" if self.ctx.should_run_discharged: self.ctx.discharged_calculated_data = self.ctx.wc_discharged.outputs.output_parameters self.ctx.charged_calculated_data = self.ctx.wc_charged.outputs.output_parameters self.ctx.discharged_relaxed_structure = self.ctx.wc_discharged.outputs.structure self.ctx.charged_relaxed_structure = self.ctx.wc_charged.outputs.structure else: self.ctx.charged_calculated_data = self.ctx.wc_charged.outputs.output_parameters self.ctx.charged_relaxed_structure = self.ctx.wc_charged.outputs.structure self.out( 'cathode_props', calculate_cathode_props( #pylint: disable=unexpected-keyword-arg self.ctx.discharged_calculated_data, self.ctx.charged_calculated_data, self.ctx.discharged_relaxed_structure, self.ctx.charged_relaxed_structure, self.inputs.anode, metadata={ 'label': 'calculate_cathode_props', 'description': 'calcfunction for calculate cathode properties', 'call_link_label': 'run_calculate_cathode_props' } ) ) self.report( 'VaspCatMatWorkChain is successfully finished! Output Dict <{}>'.format(self.outputs['cathode_props'].pk) )
# EOF