ipynb download badge. Binder badge

GitHub issues by-label

PdosPlot

[1]:
import sisl
import sisl.viz

# This is just for convenience to retreive files
siesta_files = (
    sisl._environ.get_environ_variable("SISL_FILES_TESTS") / "sisl" / "io" / "siesta"
)

We are going to get the PDOS from a SIESTA .PDOS file, but we could get it from some other source, e.g. a hamiltonian.

[2]:
plot = sisl.get_sile(siesta_files / "SrTiO3.PDOS").plot(Erange=[-10, 10])
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[2], line 1
----> 1 plot = sisl.get_sile(siesta_files / "SrTiO3.PDOS").plot(Erange=[-10, 10])

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/viz/_plotables.py:56, in ObjectPlotHandler.__call__(self, *args, **kwargs)
     52 if self._default is None:
     53     raise TypeError(
     54         f"No default plotting function has been defined for {self._obj.__class__.__name__}."
     55     )
---> 56 return getattr(self, self._default)(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/_dispatcher.py:65, in AbstractDispatch.__call__(self, *args, **kwargs)
     63 def __call__(self, *args, **kwargs):
     64     _log.debug(f"call {self.__class__.__name__}{args}", extra={"obj": self})
---> 65     return self.dispatch(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/viz/_plotables.py:64, in PlotDispatch.dispatch(self, *args, **kwargs)
     62 def dispatch(self, *args, **kwargs):
     63     """Runs the plotting function by passing the object instance to it."""
---> 64     return self._plot(self._obj, *args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/viz/_plotables.py:312, in register_data_source.<locals>._plot(obj, __params_info, __signature, *args, **kwargs)
    309     if k not in data_kwargs:
    310         data_kwargs[k] = v
--> 312 data = data_source_cls.new(obj, *args, **data_kwargs)
    314 plot_kwargs = bound.arguments.pop(params_info["plot_var_kwarg"], {})
    316 return plot_cls(**{setting_key: data, **bound.arguments, **plot_kwargs})

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/functools.py:946, in singledispatchmethod.__get__.<locals>._method(*args, **kwargs)
    944 def _method(*args, **kwargs):
    945     method = self.dispatcher.dispatch(args[0].__class__)
--> 946     return method.__get__(obj, cls)(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/viz/data/pdos.py:274, in PDOSData.from_siesta_pdos(cls, pdos_file)
    272 """Gets the PDOS from a SIESTA PDOS file"""
    273 # Get the info from the .PDOS file
--> 274 geometry, E, PDOS = pdos_file.read_data()
    276 return cls.new(PDOS, geometry, E)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/io/sile.py:672, in sile_fh_open.<locals>._wrapper.<locals>.pre_open(self, *args, **kwargs)
    669     def _reset(self):
    670         pass
--> 672 with self:
    673     # REMARK this requires the __enter__ to seek(0)
    674     # for the file, and currently it does
    675     _reset(self)
    676     return func(self, *args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/io/sile.py:1018, in Sile.__enter__(self)
   1016 def __enter__(self):
   1017     """Opens the output file and returns it self"""
-> 1018     self._open()
   1019     return self

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/site-packages/sisl/io/sile.py:1009, in Sile._open(self)
   1007             self.fh = gzip.open(str(self.file), mode=self._mode)
   1008     else:
-> 1009         self.fh = self.file.open(self._mode)
   1011 # the file should restart the file-read (as per instructed)
   1012 self._line = 0

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/stable/lib/python3.12/pathlib.py:1014, in Path.open(self, mode, buffering, encoding, errors, newline)
   1012 if "b" not in mode:
   1013     encoding = io.text_encoding(encoding)
-> 1014 return io.open(self, mode, buffering, encoding, errors, newline)

FileNotFoundError: [Errno 2] No such file or directory: '_THIS_DIRECTORY_DOES_NOT_EXIST_/sisl/io/siesta/SrTiO3.PDOS'

By default, a PDOS plot shows the total density of states:

[3]:
plot
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 plot

NameError: name 'plot' is not defined

PDOS groups

There’s a very important setting in the PdosPlot: groups. This setting expects a list of orbital groups, where each group is a dictionary that can specify - species - atoms - orbitals (the orbital name) - n, l, m (the quantum numbers) - Z (the Z shell of the orbital) - spin

involved in the PDOS line that you want to draw. Apart from that, a group also accepts the name, color, linewidth and dash keys that manage the aesthetics of the line and reduce, which indicates the method to use for accumulating orbital contributions: "mean" averages over orbitals while "sum" simply accumulates all contributions. Finally, scale lets you multiply the DOS of the group by whatever factor you want.

Here is an example of how to use the groups setting to create a line that displays the Oxygen 2p PDOS:

[4]:
plot.update_inputs(
    groups=[{"name": "My first PDOS (Oxygen)", "species": ["O"], "n": 2, "l": 1}]
)
# or (it's equivalent)
plot.update_inputs(
    groups=[
        {
            "name": "My first PDOS (Oxygen)",
            "species": ["O"],
            "orbitals": ["2pzZ1", "2pzZ2", "2pxZ1", "2pxZ2", "2pyZ1", "2pyZ2"],
        }
    ]
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 plot.update_inputs(
      2     groups=[{"name": "My first PDOS (Oxygen)", "species": ["O"], "n": 2, "l": 1}]
      3 )
      4 # or (it's equivalent)
      5 plot.update_inputs(
      6     groups=[
      7         {
   (...)
     12     ]
     13 )

NameError: name 'plot' is not defined

And now we are going to create three lines, one for each species

[5]:
plot.update_inputs(
    groups=[
        {
            "name": "Oxygen",
            "species": ["O"],
            "color": "darkred",
            "dash": "dash",
            "reduce": "mean",
        },
        {
            "name": "Titanium",
            "species": ["Ti"],
            "color": "gray",
            "size": 3,
            "reduce": "mean",
        },
        {"name": "Sr", "species": ["Sr"], "color": "green", "reduce": "mean"},
    ],
    Erange=[-5, 5],
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 plot.update_inputs(
      2     groups=[
      3         {
      4             "name": "Oxygen",
      5             "species": ["O"],
      6             "color": "darkred",
      7             "dash": "dash",
      8             "reduce": "mean",
      9         },
     10         {
     11             "name": "Titanium",
     12             "species": ["Ti"],
     13             "color": "gray",
     14             "size": 3,
     15             "reduce": "mean",
     16         },
     17         {"name": "Sr", "species": ["Sr"], "color": "green", "reduce": "mean"},
     18     ],
     19     Erange=[-5, 5],
     20 )

NameError: name 'plot' is not defined

It’s interesting to note that the atoms key of each group accepts the same possibilities as the atoms argument of the Geometry methods. Therefore, you can use indices, categories, dictionaries, strings…

For example:

[6]:
# Let's import the AtomZ and AtomOdd categories just to play with them
from sisl.geom import AtomZ, AtomOdd

plot.update_inputs(
    groups=[
        {"atoms": [0, 1], "name": "Atoms 0 and 1"},
        {"atoms": {"Z": 8}, "name": "Atoms with Z=8"},
        {"atoms": AtomZ(8) & ~AtomOdd(), "name": "Oxygens with even indices"},
    ]
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 4
      1 # Let's import the AtomZ and AtomOdd categories just to play with them
      2 from sisl.geom import AtomZ, AtomOdd
----> 4 plot.update_inputs(
      5     groups=[
      6         {"atoms": [0, 1], "name": "Atoms 0 and 1"},
      7         {"atoms": {"Z": 8}, "name": "Atoms with Z=8"},
      8         {"atoms": AtomZ(8) & ~AtomOdd(), "name": "Oxygens with even indices"},
      9     ]
     10 )

NameError: name 'plot' is not defined

Easy and fast DOS splitting

As you might have noticed, sometimes it might be cumbersome to build all the groups you want. If your needs are simple and you don’t need the flexibility of defining every parameter by yourself, there is a set of methods that will help you explore your PDOS data faster than ever before. These are: split_DOS, split_groups, update_groups, remove_groups and add_groups.

Let’s begin with split_DOS. As you can imagine, this method splits the density of states:

[7]:
plot.split_DOS()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 plot.split_DOS()

NameError: name 'plot' is not defined

By default, it splits on the different species, but you can use the on argument to change that.

[8]:
plot.split_DOS(on="atoms")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 1
----> 1 plot.split_DOS(on="atoms")

NameError: name 'plot' is not defined

Now we have the contribution of each atom.

But here comes the powerful part: split_DOS accepts as keyword arguments all the keys that a group accepts. Then, it adds that extra constrain to the splitting by adding the value to each group. So, if we want to get the separate contributions of all oxygen atoms, we can impose an extra constraint on species:

[9]:
plot.split_DOS(on="atoms", species=["O"], name="Oxygen $atoms")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 1
----> 1 plot.split_DOS(on="atoms", species=["O"], name="Oxygen $atoms")

NameError: name 'plot' is not defined

and then we have only the oxygen atoms, which are all equivalent.

Note that we also set a name for all groups, with the additional twist that we used the templating supported by split_DOS. If you are splitting on parameter, you can use $parameter inside your name and the method will replace it with the value for each group. In this case parameter was atoms, but it could be anything you are splitting the DOS on.

You can also exclude some values of the parameter you are splitting on:

[10]:
plot.split_DOS(on="atoms", exclude=[1, 3])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 1
----> 1 plot.split_DOS(on="atoms", exclude=[1, 3])

NameError: name 'plot' is not defined

Or indicate the only values that you want:

[11]:
plot.split_DOS(on="atoms", only=[0, 2])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 1
----> 1 plot.split_DOS(on="atoms", only=[0, 2])

NameError: name 'plot' is not defined

Finally, if you want to split on multiple parameters at the same time, you can use + between different parameters. For example, to get all the oxygen orbitals:

[12]:
plot.split_DOS(on="n+l+m", species=["O"], name="Oxygen")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 1
----> 1 plot.split_DOS(on="n+l+m", species=["O"], name="Oxygen")

NameError: name 'plot' is not defined

Managing existing groups

Not only you can create groups easily with split_DOS, but it’s also easy to manage the groups that you have created.

The methods that help you accomplish this are split_groups, update_groups, remove_groups. All three methods accept an undefined number of arguments that are used to select the groups you want to act on. You can refer to groups by their name (using a str) or their position (using an int). It’s very easy to understand with examples. Then, keyword arguments depend on the functionality of each method.

For example, let’s say that we have splitted the DOS on species

[13]:
plot.split_DOS(name="$species")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[13], line 1
----> 1 plot.split_DOS(name="$species")

NameError: name 'plot' is not defined

and we want to remove the Sr and O lines. That’s easy:

[14]:
plot.remove_groups("Sr", 2)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[14], line 1
----> 1 plot.remove_groups("Sr", 2)

NameError: name 'plot' is not defined

We have indicated that we wanted to remove the group with name "Sr" and the 2nd group. Simple, isn’t it?

Now that we know how to indicate the groups that we want to act on, let’s use it to get the total Sr contribution, and then the Ti and O contributions splitted by n and l.

It sounds difficult, but it’s actually not. Just split the DOS on species:

[15]:
plot.split_DOS(name="$species", reduce="mean")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[15], line 1
----> 1 plot.split_DOS(name="$species", reduce="mean")

NameError: name 'plot' is not defined

And then use split_groups to split only the groups that we want to split:

[16]:
plot.split_groups("Sr", 2, on="n+l", dash="dot")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 plot.split_groups("Sr", 2, on="n+l", dash="dot")

NameError: name 'plot' is not defined

Notice how we’ve also set dash for all the groups that split_groups has generated. We can do this because split_groups works exactly as split_DOS, with the only difference that splits specific groups.

Just as a last thing, we will let you figure out how update_groups works:

[17]:
plot.update_groups("Ti", color="red", size=4)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 1
----> 1 plot.update_groups("Ti", color="red", size=4)

NameError: name 'plot' is not defined

We hope you enjoyed what you learned!


This next cell is just to create the thumbnail for the notebook in the docs

[18]:
thumbnail_plot = plot.update_groups("Ti", color=None, size=1)

if thumbnail_plot:
    thumbnail_plot.show("png")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[18], line 1
----> 1 thumbnail_plot = plot.update_groups("Ti", color=None, size=1)
      3 if thumbnail_plot:
      4     thumbnail_plot.show("png")

NameError: name 'plot' is not defined

[ ]: