.. _logging:

Logging
=======

Tkwant provides a logger to record events and internal states during a calculation.
Enabling the logger, which by default is switched off,
can help to find errors in the user code and also facilitate debugging tkwant.

Logging is enabled by the following lines of code:

.. jupyter-execute::

    import tkwant
    import logging

    tkwant.logging.handler = tkwant.logging.debug_handler

By default, only warning messages are logged, which typically look like this:

.. jupyter-execute::
    :hide-code:

    print("WARNING:tkwant.manybody:1452:rank=0: no occupied states found, the chemical potential is probably wrong.")

The verbosity of the logger can increased to print also status and debug information, see `Level`_.
Note that logging handler must be set *after* importing the tkwant module and
*before* executing any tkwant code. A complete `Example`_ is shown below.

Tkwant's logger is based on logging module of the Python standard library.
We refer to the
`Logging HOWTO 
<https://docs.python.org/3/howto/logging.html#logging-basic-tutorial>`__
tutorial for more information about Python's logging module.

Handlers
~~~~~~~~

The handler specify the format of the logging output.
Tkwant has implemented two predefined stream handlers:
``tkwant.logging.debug_handler`` and ``tkwant.logging.simple_handler``.

The **debug_handler** can be set with:

.. jupyter-execute::

    tkwant.logging.handler = tkwant.logging.debug_handler

With this handler, a typical log output looks like:

.. jupyter-execute::
    :hide-code:

    print("INFO:tkwant.manybody:229:rank=0: distribution function: zero-temperature fermi-dirac")

The format is:

 *level*: *module-name*: *line-number*: *MPI-rank*: *log message*

Note that the MPI rank corresponds to the rank of tkwant's global MPI communicator
that might be different to the rank of sub-communicators.


The **simple_handler** is less verbose and prints only the log message.
It can be set with:

.. jupyter-execute::

    tkwant.logging.handler = tkwant.logging.simple_handler

With this handler, a typical log output looks like:

.. jupyter-execute::
    :hide-code:

    print("distribution function: zero-temperature fermi-dirac")

The format is:

 *log message*

Alternative handlers whose API matches the logging module of the
Python standard library can be set in the same way.


Level
~~~~~

Logging has several severity levels to control which level of log messages are
recorded. By default, tkwant is logging only warning messages
(corresponding to ``logging.WARNING``).
To change the log level, one has set ``tkwant.logging.level`` to a level,
as defined in the Python logging module.
As an example, to log also progress information, the additional line of code
must be added:

.. jupyter-execute::

    tkwant.logging.level = logging.INFO

Again, the logging level must be set *after* importing the tkwant module and
*before* executing any tkwant code. The most verbose output is generated by setting
the level to ``logging.DEBUG``:

.. jupyter-execute::

    tkwant.logging.level = logging.DEBUG



Filters
~~~~~~~

The logging output can be filtered to reduce the output to specific parts.
To log only logging events triggered by a certain module, as e.g. ``tkwant.leads``,
on can set

.. jupyter-execute::

    tkwant.logging.filter = logging.Filter('tkwant.leads')

Alternatively, one can also define a filter function. The following code
logs only the messages from MPI with rank zero (which is typically the root rank):

.. jupyter-execute::

    def rank_filter(record):
        return True if record.rank == 0 else False

    tkwant.logging.filter = rank_filter

If one likes to log only messages containing the word *interval* one can use

.. jupyter-execute::

    def message_filter(record):
        return True if 'interval' in record.getMessage() else False

    tkwant.logging.filter = message_filter

Again, the filter must be set *after* importing the tkwant module and *before*
executing any tkwant code. The documentation of the Python standard library logging
module provides more information to write custom filters.

Example
~~~~~~~

As an example, we show the toy example from
:ref:`getting_started` with enabled logging and the generated output.
Note that all logging output is generated by the call to ``tkwant.manybody.State()``
and reveals the preprocessing steps of the automatic high-level approach.

.. jupyter-execute::

    import numpy as np
    import matplotlib.pyplot as plt

    import kwant
    import tkwant

    #-------------------- enable logging --------------------------------
    import logging

    def rank_filter(record):
        return True if record.rank == 0 else False

    tkwant.logging.handler = tkwant.logging.debug_handler
    tkwant.logging.level = logging.DEBUG
    tkwant.logging.filter = rank_filter
    #--------------------------------------------------------------------


    def v(time, tau=8):
        """Time dependent perturbation V(t)"""
        if time < tau:
            return time / tau
        return 1


    def create_system(length):

        def onsite_potential(site, time):
            """Time dependent onsite potential (static part + V(t))"""
            return 1 + v(time)

        # system building
        lat = kwant.lattice.square(a=1, norbs=1)
        syst = kwant.Builder()

        # central scattering region
        syst[(lat(x, 0) for x in range(length))] = 1
        syst[lat.neighbors()] = -1
        # time dependent onsite-potential at the leftmost site
        syst[lat(0, 0)] = onsite_potential

        # add leads
        sym = kwant.TranslationalSymmetry((-1, 0))
        lead_left = kwant.Builder(sym)
        lead_left[lat(0, 0)] = 1
        lead_left[lat.neighbors()] = -1
        syst.attach_lead(lead_left)
        syst.attach_lead(lead_left.reversed())

        return syst


    # parameters
    tmax = 20
    length = 5

    # create system
    syst = create_system(length).finalized()

    times = np.linspace(0, tmax)

    # define an observable
    density_operator = kwant.operator.Density(syst)

    # do the actual tkwant simulation
    state = tkwant.manybody.State(syst, tmax=tmax)

    densities = []
    for time in times:
        state.evolve(time)
        density = state.evaluate(density_operator)
        densities.append(density)

    # plot the result
    plt.plot(times, densities)
    plt.xlabel(r'time $t$')
    plt.ylabel(r'charge density $n$')
    plt.show()


References
----------

`Logging HOWTO 
<https://docs.python.org/3/howto/logging.html#logging-basic-tutorial>`__

`Python standard library logging module documentation
<https://docs.python.org/3/library/logging.html>`__

