Logging Example

This notebook shows how to use the logging feature of power-grid-model. So far, there are two types of loggers available:

Type

What it captures

LoggerType.info

Timestamped debug messages. Useful for diagnosing various issues

LoggerType.benchmark

Per calculation phase timing totals. Useful for performance analysis

Important: loggers provide hints, not conclusions. Output is intended for advanced users and may change between releases.

Logging has a performance cost, which is why it is opt-in: loggers are not active by default.

Example Network

We reuse the 3-node network from the Power Flow Example:

 -----------------------line_8---------------
 |                                          |
node_1 ---line_3--- node_2 ----line_5---- node_6
 |                    |                     |
source_10          sym_load_4           sym_load_7

Imports

from power_grid_model import (
    AttributeType,
    CalculationMethod,
    ComponentType,
    DatasetType,
    LoadGenType,
    Logger,
    LoggerType,
    PowerGridModel,
    initialize_array,
)

Build the Input Dataset

node = initialize_array(DatasetType.input, ComponentType.node, 3)
node[AttributeType.id] = [1, 2, 6]
node[AttributeType.u_rated] = [10.5e3, 10.5e3, 10.5e3]

line = initialize_array(DatasetType.input, ComponentType.line, 3)
line[AttributeType.id] = [3, 5, 8]
line[AttributeType.from_node] = [1, 2, 1]
line[AttributeType.to_node] = [2, 6, 6]
line[AttributeType.from_status] = [1, 1, 1]
line[AttributeType.to_status] = [1, 1, 1]
line[AttributeType.r1] = [0.25, 0.25, 0.25]
line[AttributeType.x1] = [0.2, 0.2, 0.2]
line[AttributeType.c1] = [10e-6, 10e-6, 10e-6]
line[AttributeType.tan1] = [0.0, 0.0, 0.0]
line[AttributeType.i_n] = [1000, 1000, 1000]

sym_load = initialize_array(DatasetType.input, ComponentType.sym_load, 2)
sym_load[AttributeType.id] = [4, 7]
sym_load[AttributeType.node] = [2, 6]
sym_load[AttributeType.status] = [1, 1]
sym_load[AttributeType.type] = [LoadGenType.const_power, LoadGenType.const_power]
sym_load[AttributeType.p_specified] = [20e6, 10e6]
sym_load[AttributeType.q_specified] = [5e6, 2e6]

source = initialize_array(DatasetType.input, ComponentType.source, 1)
source[AttributeType.id] = [10]
source[AttributeType.node] = [1]
source[AttributeType.status] = [1]
source[AttributeType.u_ref] = [1.0]

input_data = {
    ComponentType.node: node,
    ComponentType.line: line,
    ComponentType.sym_load: sym_load,
    ComponentType.source: source,
}

model = PowerGridModel(input_data)

Text Logger

A LoggerType.info logger captures diagnostic messages emitted during any stage of calculation. Each line is timestamped and tagged with the event code.

Usage: use Logger as a context manager. On entry the logger is registered and active while on exit it is unregistered as renderend inactive. The output buffer is preserved after the with block and is accessible via logger.output.

with Logger(LoggerType.info) as info_logger:
    output_data = model.calculate_power_flow(
        symmetric=True,
        calculation_method=CalculationMethod.newton_raphson,
    )

print(info_logger.output)
[2026-10-06 13:50:26.923Z] Tag:2000: 0.000658
[2026-10-06 13:50:26.923Z] Tag:3100: 0.000001
[2026-10-06 13:50:26.924Z] Tag:3211: 0.000872
[2026-10-06 13:50:26.924Z] Tag:3233: 0.000007
[2026-10-06 13:50:26.924Z] Tag:3241: 0.000001
[2026-10-06 13:50:26.924Z] Tag:3251: 0.000083
[2026-10-06 13:50:26.924Z] Tag:3233: 0.000000
[2026-10-06 13:50:26.924Z] Tag:3241: 0.000001
[2026-10-06 13:50:26.924Z] Tag:3251: 0.000000
[2026-10-06 13:50:26.924Z] Tag:3233: 0.000000
[2026-10-06 13:50:26.924Z] Tag:3241: 0.000000
[2026-10-06 13:50:26.924Z] Tag:3251: 0.000000
[2026-10-06 13:50:26.924Z] Tag:3290: 0.000003
[2026-10-06 13:50:26.924Z] Tag:3200: 0.000990
[2026-10-06 13:50:26.924Z] Tag:3253: 3
[2026-10-06 13:50:26.924Z] Tag:3000: 0.001001
[2026-10-06 13:50:26.924Z] Tag:4000: 0.000008
[2026-10-06 13:50:26.924Z] Tag:100: 0.002803

Benchmark Logger

A LoggerType.benchmark logger records cumulative time spent in each calculation phase. The output is tab-separated with two columns: EVENT_CODE and VALUE (seconds).

Unlike the info logger, the benchmark logger ignores string messages entirely. It only accumulates numeric (double) values of duration from internal timer events.

with Logger(LoggerType.benchmark) as bench_logger:
    output_data = model.calculate_power_flow(
        symmetric=True,
        calculation_method=CalculationMethod.newton_raphson,
    )

print(bench_logger.output)
100	4.3974e-05
2000	5.725e-06
3000	1.9704e-05
3200	1.7246e-05
3211	6.527e-06
3233	1.052e-06
3241	1.535e-06
3251	1.492e-06
3253	3
3290	4.35e-06
4000	1.1787e-05

Multiple Simultaneous Loggers

Multiple logger types can be combined in a single with statement. Each logger independently captures its own output.

with (
    Logger(LoggerType.info) as info_logger2,
    Logger(LoggerType.benchmark) as bench_logger2,
):
    output_data = model.calculate_power_flow(
        symmetric=True,
        calculation_method=CalculationMethod.newton_raphson,
    )

print("=== Info logger ===")
print(info_logger2.output)
print("=== Benchmark logger ===")
print(bench_logger2.output)
=== Info logger ===
[2026-10-06 13:50:26.950Z] Tag:2000: 0.000008
[2026-10-06 13:50:26.950Z] Tag:3211: 0.000006
[2026-10-06 13:50:26.950Z] Tag:3233: 0.000001
[2026-10-06 13:50:26.950Z] Tag:3241: 0.000001
[2026-10-06 13:50:26.950Z] Tag:3251: 0.000001
[2026-10-06 13:50:26.950Z] Tag:3233: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3241: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3251: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3233: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3241: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3251: 0.000000
[2026-10-06 13:50:26.950Z] Tag:3290: 0.000008
[2026-10-06 13:50:26.950Z] Tag:3200: 0.000035
[2026-10-06 13:50:26.950Z] Tag:3253: 3
[2026-10-06 13:50:26.950Z] Tag:3000: 0.000040
[2026-10-06 13:50:26.950Z] Tag:4000: 0.000032
[2026-10-06 13:50:26.950Z] Tag:100: 0.000140

=== Benchmark logger ===
100	0.000139593
2000	7.813e-06
3000	3.9733e-05
3200	3.4965e-05
3211	5.763e-06
3233	1.175e-06
3241	1.548e-06
3251	1.45e-06
3253	3
3290	7.65e-06
4000	3.191e-05

Accumulation and Clearing Content

Output accumulates across all calculations inside a single with block. Call logger.clear_content() inside the block to reset between calculations without unregistering.

with Logger(LoggerType.benchmark) as acc_logger:
    # Run four calculations while output accumulates inside the with block
    for _ in range(4):
        model.calculate_power_flow(symmetric=True)

print(f"Combined output ({acc_logger.output.count('\n')} lines):")
print(acc_logger.output)
Combined output (11 lines):
100	0.000104808
2000	1.3031e-05
3000	4.802e-05
3200	4.2484e-05
3211	1.3951e-05
3233	3.622e-06
3241	5.827e-06
3251	3.873e-06
3253	3
3290	1e-05
4000	2.6465e-05
# clear_content() resets the buffer without unregistering. This can be useful between runs
with Logger(LoggerType.benchmark) as acc_logger:
    model.calculate_power_flow(symmetric=True)
    print(f"After first calc ({acc_logger.output.count('\n')} lines)")

    acc_logger.clear_content()
    print(f"After clear_content: {acc_logger.output!r}")

    model.calculate_power_flow(symmetric=True)

print(f"After second calc ({acc_logger.output.count('\n')} lines):")
print(acc_logger.output)
After first calc (11 lines)
After clear_content: ''
After second calc (11 lines):
100	4.4643e-05
2000	5.386e-06
3000	2.0341e-05
3200	1.8172e-05
3211	6.252e-06
3233	1.266e-06
3241	1.805e-06
3251	3.294e-06
3253	3
3290	3.861e-06
4000	1.0799e-05

Reuse Across Runs

A Logger object can be re-entered across multiple with blocks. Each entry re-registers on the handle; each exit unregisters. Call logger.clear_content() between runs to start with a fresh buffer.

reuse_logger = Logger(LoggerType.benchmark)

with reuse_logger:
    model.calculate_power_flow(symmetric=True)
lines_run1 = reuse_logger.output.count("\n")

reuse_logger.clear_content()

with reuse_logger:
    model.calculate_power_flow(symmetric=True)
lines_run2 = reuse_logger.output.count("\n")

print(f"Run 1: {lines_run1} lines, Run 2: {lines_run2} lines")
Run 1: 11 lines, Run 2: 11 lines

Python Logging Integration

Pass python_logger= to route output through the standard logging module. On __exit__, accumulated lines are flushed to python_logger. One log is recorded per line at the configured level and the internal buffer is cleared. No manual flush needed.

import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
py_logger = logging.getLogger("power_grid_model")

with Logger(LoggerType.info, python_logger=py_logger) as py_log_logger:
    py_logger.info("Starting power flow calculation")
    model.calculate_power_flow(
        symmetric=True,
        calculation_method=CalculationMethod.newton_raphson,
    )

# __exit__ flushed each line to py_logger automatically; buffer is now empty
print(f"Buffer after exit: {py_log_logger.output!r}")
INFO power_grid_model: Starting power flow calculation
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:2000: 0.000006
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3211: 0.000005
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3233: 0.000001
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3241: 0.000001
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3251: 0.000001
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3233: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3241: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3251: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3233: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3241: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3251: 0.000000
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3290: 0.000003
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3200: 0.000022
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3253: 3
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:3000: 0.000025
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:4000: 0.000010
INFO power_grid_model: [2026-10-06 13:50:27.028Z] Tag:100: 0.000068
Buffer after exit: ''

Batch Calculations

Loggers work seamlessly with batch calculations as well. It internally handles logging of multi-threaded calculations. In case of exceptions, correct output from loggers is not guaranteed.

# Create a small batch update: vary the load in node_2 across 3 scenarios
load_update = initialize_array(DatasetType.update, ComponentType.sym_load, (3, 3))
load_update[AttributeType.id] = [4, 4, 4]
load_update[AttributeType.p_specified] = [10e6, 20e6, 30e6]
update_data = {ComponentType.sym_load: load_update}

with Logger(LoggerType.benchmark) as batch_bench:
    batch_output = model.calculate_power_flow(
        update_data=update_data,
        symmetric=True,
        threading=0,  # parallel
    )

print(batch_bench.output)
100	0.00243333
1100	0.000238997
1200	0.000301152
1220	5.8282e-05
1230	8.8664e-05
1231	3.2429e-05
2000	2.1209e-05
3000	8.1919e-05
3200	7.464e-05
3211	2.2738e-05
3233	5.423e-06
3241	6.964e-06
3251	5.219e-06
3253	3
3290	2.7732e-05
4000	3.5953e-05

Usage notes

  • Do not destroy (or let Python GC collect) a Logger inside an active with block. It triggers a ResourceWarning and it is inefficient as the logger is not broadcasting but active internally.

  • Do not use the same logger from multiple user threads simultaneously. (The internal C-level batch calculation threads are safe.)