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 |
|---|---|
|
Timestamped debug messages. Useful for diagnosing various issues |
|
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
Loggerinside an activewithblock. It triggers aResourceWarningand 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.)