Source code for nanotools.localdos

# -*- coding: utf-8 -*-
"""
Created on 2020-05-11

@author: Vincent Michaud-Rioux
"""

from attr import field
from nanotools.base import Base, Quantity
from nanotools.utils import to_quantity, ureg
from nanotools.energy import Energy
from nanotools.io.calculators import solve_generic
from nanotools.solver import Solver
from nanotools.system import System
from nanotools.totalenergy import TotalEnergy
from nanotools.utils import read_field, dict_converter
import attr


[docs] @attr.s class LDOSData(Base): """``LocalDOS`` data class. Attributes: energies: Energies at which the LDOS is calculated. """ energies: Quantity = field( default=None, converter=attr.converters.optional(lambda x: to_quantity(x, "eV", shape=(-1))), validator=attr.validators.optional( attr.validators.instance_of(Quantity), ), ) def set_energies(self, energies): self.energies = to_quantity(energies, "eV", shape=(-1))
[docs] @attr.s class LocalDOS(Base): """``LocalDOS`` class. Examples:: from nanotools import LocalDOS import numpy as np from nanotools.utils import ureg calc = LocalDOS.from_totalenergy("nano_scf_out.json") calc.system.kpoint.set_grid([45,45,1]) calc.ldos.energies = [(calc.energy.efermi.m - 0.001)] * ureg.electron_volt calc.solve() calc.plot_ldos(filename="ldos.png", show=True) Attributes: system: Object containing system related parameters. ldos: Object containing the local density of states data. energy: Object containing the total energy and its derivatives (force, stress, etc.). solver: Object containing solver related parameters. """ # input is dictionary with default constructor system: System = attr.ib( converter=lambda d: dict_converter(d, System), validator=attr.validators.instance_of(System), ) # optional ldos: LDOSData = attr.ib( factory=LDOSData, converter=lambda d: dict_converter(d, LDOSData), validator=attr.validators.instance_of(LDOSData), ) energy: Energy = attr.ib( factory=Energy, converter=lambda d: dict_converter(d, Energy), validator=attr.validators.instance_of(Energy), ) solver: Solver = attr.ib( factory=Solver, converter=lambda d: dict_converter(d, Solver), validator=attr.validators.instance_of(Solver), ) classname: str = attr.ib() @classname.default def _classname_default_value(self): return self.__class__.__name__ def __attrs_post_init__(self): self._validate() @classmethod def from_totalenergy(cls, totalenergy, **kwargs): if isinstance(totalenergy, TotalEnergy): pass else: totalenergy = TotalEnergy.read(totalenergy) sys = totalenergy.system.copy() calc = cls(sys, solver=totalenergy.solver, **kwargs) calc.energy = totalenergy.energy.copy() return calc
[docs] def solve(self, input="nano_ldos_in", output="nano_ldos_out"): """Performs a non.self-consistent calculation calling ``rescuplus``. Args: filename (str): The object is saved to an input file ``filename`` which is read by ``rescuplus``. output (str): The results (with various extensions) are moved to files ``output`` and the results are loaded to the object. """ self._validate() self._check_ldos() output = solve_generic(self, "ldos", input, output) self._update(output + ".json")
def _check_ldos(self): if self.system.kpoint.type != "full": raise ValueError( "system.kpoint.type must be set to full for an ldos calculation." ) def _validate(self): if self.solver.mpidist.kptprc is not None: if self.solver.mpidist.kptprc > 1: # raise ValueError("solver.mpidist.kptprc must be None or 1 in an ldos calculation.") print( "Warning: solver.mpidist.kptprc must be None or 1 in an ldos calculation." ) self.solver.mpidist.kptprc = 1
[docs] def plot_ldos(self, value=None, index=0, filename=None, show=True): """Generates an isosurface plot of the local density of states. Args: value (float, optional): The density level at which to generate the isosurface. If value is not provided (value=None), the function automatically selects a default value based on the maximum LDOS value. Specifically, it sets value to 75% of the maximum LDOS value (values.max().m * 0.75). This default ensures a meaningful isosurface is plotted, avoiding very high or low extremes. index (int, optional): Index of the LDOS dataset to be used. Defaults to 0. filename (str, optional): If not None, then the figure is saved to filename. show (bool, optional): If True, block and show figure. If False, do not show figure. Returns: fig (:obj:`matplotlib.figure.Figure`): A figure handle. """ import plotly.graph_objects as go import plotly.io as pio import numpy as np h5file = self.solver.restart.densityPath values = read_field(h5file, f"ldos/{index+1}/total") xyz = self.system.cell.get_grids() g = self.system.cell.grid X, Y, Z = np.mgrid[range(g[0]), range(g[1]), range(g[2])] if value is not None: value = value else: value = values.max().m * 0.75 fig = go.Figure(data=go.Isosurface( x=X.flatten(), y=Y.flatten(), z=Z.flatten(), value=values.flatten(), isomin=value*0.99, isomax=value*1.01, surface_count=1, colorscale='Viridis', caps=dict(x_show=False, y_show=False) )) fig.update_layout( scene=dict( xaxis_title='X', yaxis_title='Y', zaxis_title='Z' )) if show: fig.show() if filename is not None: pio.write_image(fig, filename) return fig