What is Grid2Op?
Grid2Op is a platform for developing and testing reinforcement learning algorithms for power grid operation. PyPowSyBl can serve as a backend engine, providing accurate power flow simulations for Grid2Op environments.Getting Started
Creating a Grid2Op Backend
import pypowsybl as pp
from pypowsybl import grid2op
# Create a network
network = pp.network.create_eurostag_tutorial_example1_network()
# Run initial power flow
pp.loadflow.run_ac(network)
# Create Grid2Op backend
with grid2op.Backend(network) as backend:
# Backend is ready to use
print(f"Backend initialized for network: {backend.network.id}")
Backend Configuration Options
import pypowsybl as pp
from pypowsybl import grid2op
network = pp.network.create_ieee14()
pp.loadflow.run_ac(network)
# Create backend with custom options
with grid2op.Backend(
network,
consider_open_branch_reactive_flow=False,
check_isolated_and_disconnected_injections=True,
buses_per_voltage_level=2,
connect_all_elements_to_first_bus=True
) as backend:
print("Backend created with custom configuration")
The Grid2Op backend uses a context manager (
with statement) to ensure proper resource cleanup.Reading Network Data
Getting String Values
Create backend and query data
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get voltage level names
vl_names = backend.get_string_value(grid2op.StringValueType.VOLTAGE_LEVEL_NAME)
print("Voltage levels:", vl_names)
# Output: ['VLGEN', 'VLHV1', 'VLHV2', 'VLLOAD']
# Get load names
load_names = backend.get_string_value(grid2op.StringValueType.LOAD_NAME)
print("Loads:", load_names)
# Output: ['LOAD']
# Get generator names
gen_names = backend.get_string_value(grid2op.StringValueType.GENERATOR_NAME)
print("Generators:", gen_names)
# Output: ['GEN', 'GEN2']
# Get branch names
branch_names = backend.get_string_value(grid2op.StringValueType.BRANCH_NAME)
print("Branches:", branch_names)
# Output: ['NGEN_NHV1', 'NHV1_NHV2_1', 'NHV1_NHV2_2', 'NHV2_NLOAD']
Getting Integer Values
import pypowsybl as pp
from pypowsybl import grid2op
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get voltage level numbers for loads
load_vl_nums = backend.get_integer_value(grid2op.IntegerValueType.LOAD_VOLTAGE_LEVEL_NUM)
print("Load voltage level numbers:", load_vl_nums)
# Output: [3]
# Get voltage level numbers for generators
gen_vl_nums = backend.get_integer_value(grid2op.IntegerValueType.GENERATOR_VOLTAGE_LEVEL_NUM)
print("Generator voltage level numbers:", gen_vl_nums)
# Output: [0, 0]
# Get branch voltage level numbers (both ends)
branch_vl1 = backend.get_integer_value(grid2op.IntegerValueType.BRANCH_VOLTAGE_LEVEL_NUM_1)
branch_vl2 = backend.get_integer_value(grid2op.IntegerValueType.BRANCH_VOLTAGE_LEVEL_NUM_2)
print("Branch voltage levels:")
print(" Side 1:", branch_vl1)
print(" Side 2:", branch_vl2)
# Get topology vector
topo_vect = backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)
print("Topology vector:", topo_vect)
Getting Power Flow Results
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get load active power
load_p = backend.get_double_value(grid2op.DoubleValueType.LOAD_P)
print(f"Load P: {load_p} MW")
# Get load reactive power
load_q = backend.get_double_value(grid2op.DoubleValueType.LOAD_Q)
print(f"Load Q: {load_q} MVAr")
# Get load voltage magnitude
load_v = backend.get_double_value(grid2op.DoubleValueType.LOAD_V)
print(f"Load voltage: {load_v} kV")
# Get load voltage angle
load_angle = backend.get_double_value(grid2op.DoubleValueType.LOAD_ANGLE)
print(f"Load angle: {load_angle} rad")
Modifying Network State
Updating Load Values
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Check initial load
initial_p = backend.get_double_value(grid2op.DoubleValueType.LOAD_P)
print(f"Initial load P: {initial_p} MW")
# Update load active power
backend.update_double_value(
grid2op.UpdateDoubleValueType.UPDATE_LOAD_P,
np.array([630.0]), # New value
np.array([True]) # Apply this change
)
# Verify update
updated_p = backend.get_double_value(grid2op.DoubleValueType.LOAD_P)
print(f"Updated load P: {updated_p} MW")
# Run power flow with new load
backend.run_pf()
# Check new branch flows
branch_p1 = backend.get_double_value(grid2op.DoubleValueType.BRANCH_P1)
print(f"\nNew branch flows: {branch_p1} MW")
Topology Modifications
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get initial topology
initial_topo = backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)
print(f"Initial topology: {initial_topo}")
# Disconnect a load (set bus to -1)
backend.update_integer_value(
grid2op.UpdateIntegerValueType.UPDATE_LOAD_BUS,
np.array([-1]),
np.array([True])
)
# Check updated topology
new_topo = backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)
print(f"Updated topology: {new_topo}")
# Disconnect generators
backend.update_integer_value(
grid2op.UpdateIntegerValueType.UPDATE_GENERATOR_BUS,
np.array([2, -1]), # Bus 2 for first gen, disconnect second
np.array([True, True])
)
# Verify changes
final_topo = backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)
print(f"Final topology: {final_topo}")
Working with IEEE 14-Bus Network
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
# Create IEEE 14-bus network (includes shunt compensators)
network = pp.network.create_ieee14()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get shunt compensator data
shunt_names = backend.get_string_value(grid2op.StringValueType.SHUNT_NAME)
print(f"Shunts: {shunt_names}")
# Output: ['B9-SH']
# Get shunt power and voltage
shunt_p = backend.get_double_value(grid2op.DoubleValueType.SHUNT_P)
shunt_q = backend.get_double_value(grid2op.DoubleValueType.SHUNT_Q)
shunt_v = backend.get_double_value(grid2op.DoubleValueType.SHUNT_V)
print(f"\nShunt compensator:")
print(f" P: {shunt_p} MW")
print(f" Q: {shunt_q} MVAr")
print(f" V: {shunt_v} kV")
# Get all load voltages
load_voltages = backend.get_double_value(grid2op.DoubleValueType.LOAD_V)
print(f"\nLoad voltages: {load_voltages} kV")
# Disconnect shunt and observe impact
backend.update_integer_value(
grid2op.UpdateIntegerValueType.UPDATE_SHUNT_BUS,
np.array([-1]),
np.array([True])
)
# Run power flow
backend.run_pf()
# Compare voltages
new_load_voltages = backend.get_double_value(grid2op.DoubleValueType.LOAD_V)
print(f"Load voltages after shunt disconnection: {new_load_voltages} kV")
Running Power Flow
AC Power Flow
import pypowsybl as pp
from pypowsybl import grid2op
network = pp.network.create_ieee14()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Run AC power flow
results = backend.run_pf(dc=False)
print(f"Power flow converged: {results[0].status}")
print(f"Iteration count: {results[0].iteration_count}")
print(f"Slack bus active power: {results[0].slack_bus_active_power_mismatch} MW")
DC Power Flow
import pypowsybl as pp
from pypowsybl import grid2op
network = pp.network.create_ieee30()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Run DC power flow (faster, linear approximation)
results = backend.run_pf(dc=True)
print(f"DC power flow status: {results[0].status}")
Power Flow with Custom Parameters
import pypowsybl as pp
from pypowsybl import grid2op
from pypowsybl.loadflow import Parameters
network = pp.network.create_ieee14()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Create custom parameters
params = Parameters(
voltage_init_mode='DC_VALUES',
transformer_voltage_control_on=True,
no_generator_reactive_limits=False,
distributed_slack=True
)
# Run with custom parameters
results = backend.run_pf(dc=False, parameters=params)
print(f"Custom power flow: {results[0].status}")
Backend Persistence and Serialization
import pypowsybl as pp
from pypowsybl import grid2op
import pickle
import tempfile
import pathlib
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Modify network state
backend.update_double_value(
grid2op.UpdateDoubleValueType.UPDATE_LOAD_P,
np.array([630.0]),
np.array([True])
)
pp.loadflow.run_ac(network)
# Save backend to file
with tempfile.TemporaryDirectory() as tmpdir:
data_file = pathlib.Path(tmpdir) / 'backend.pkl'
with open(data_file, 'wb') as f:
pickle.dump(backend, f)
# Load backend from file
with open(data_file, 'rb') as f:
with pickle.load(f) as restored_backend:
# Verify state was preserved
load_p = restored_backend.get_double_value(grid2op.DoubleValueType.LOAD_P)
print(f"Restored load P: {load_p} MW")
Backend serialization is useful for checkpointing RL training or saving simulation states.
Branch Limits and Constraints
import pypowsybl as pp
from pypowsybl import grid2op
network = pp.network.create_eurostag_tutorial_example1_network()
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
# Get branch permanent limits
limits = backend.get_double_value(grid2op.DoubleValueType.BRANCH_PERMANENT_LIMIT_A)
print(f"Branch limits (A): {limits}")
# Get branch currents
currents_1 = backend.get_double_value(grid2op.DoubleValueType.BRANCH_I1)
currents_2 = backend.get_double_value(grid2op.DoubleValueType.BRANCH_I2)
# Check for overloads
for i, (limit, i1, i2) in enumerate(zip(limits, currents_1, currents_2)):
max_current = max(i1, i2)
if max_current > limit:
branch_names = backend.get_string_value(grid2op.StringValueType.BRANCH_NAME)
print(f"\nOverload on branch {branch_names[i]}:")
print(f" Current: {max_current:.2f} A")
print(f" Limit: {limit:.2f} A")
print(f" Overload: {(max_current/limit - 1)*100:.1f}%")
Checking for Isolated Components
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
network = pp.network.create_ieee14()
pp.loadflow.run_ac(network)
# Create backend that checks for isolated injections
with grid2op.Backend(
network,
check_isolated_and_disconnected_injections=False
) as backend:
# Disconnect a branch
backend.update_integer_value(
grid2op.UpdateIntegerValueType.UPDATE_BRANCH_BUS1,
np.array([1] * 10 + [-1] + [1] * 9),
np.array([False] * 10 + [True] + [False] * 9)
)
backend.update_integer_value(
grid2op.UpdateIntegerValueType.UPDATE_BRANCH_BUS2,
np.array([1] * 10 + [-1] + [1] * 9),
np.array([False] * 10 + [True] + [False] * 9)
)
# Run power flow
backend.run_pf()
# Check topology
topo = backend.get_integer_value(grid2op.IntegerValueType.TOPO_VECT)
print(f"Topology after disconnection: {topo}")
print(f"Isolated components: {np.sum(topo == -1)}")
Complete RL Training Example
import pypowsybl as pp
from pypowsybl import grid2op
import numpy as np
def simulate_episode(network, num_steps=10):
"""
Simulate a simple episode for reinforcement learning.
"""
pp.loadflow.run_ac(network)
with grid2op.Backend(network) as backend:
episode_data = []
for step in range(num_steps):
# Get current state
load_p = backend.get_double_value(grid2op.DoubleValueType.LOAD_P)
gen_p = backend.get_double_value(grid2op.DoubleValueType.GENERATOR_P)
branch_i = backend.get_double_value(grid2op.DoubleValueType.BRANCH_I1)
# Random load variation (simulating demand changes)
load_variation = 1.0 + np.random.uniform(-0.1, 0.1, size=load_p.shape)
new_load = load_p * load_variation
# Update load
backend.update_double_value(
grid2op.UpdateDoubleValueType.UPDATE_LOAD_P,
new_load,
np.ones(len(new_load), dtype=bool)
)
# Run power flow
results = backend.run_pf()
# Record step data
episode_data.append({
'step': step,
'converged': results[0].status.name == 'CONVERGED',
'total_load': new_load.sum(),
'total_generation': gen_p.sum(),
'max_line_current': branch_i.max()
})
return episode_data
# Run simulation
network = pp.network.create_ieee14()
episode = simulate_episode(network, num_steps=5)
print("\nEpisode results:")
for data in episode:
print(f"Step {data['step']}: Load={data['total_load']:.1f} MW, "
f"Converged={data['converged']}")
This example demonstrates the basic structure for RL training. In practice, you would integrate this with Grid2Op’s full environment and action/observation spaces.
Next Steps
- Explore IEEE Networks for test scenarios
- Learn about Network Modification for dynamic environments
- Check Grid2Op documentation for full RL integration patterns
