Skip to content

TNO-Quantum/problems.n_minus_one

Repository files navigation

N-minus-1 Quantum Annealing/Gate Based Solver

Energy network operators are planning to expand their grid capacity over the next decade by an amount comparable to what was achieved in the past 100 years. This rapid growth is driven by the increasing adoption of technologies such as solar panels and electric vehicles, which introduce additional complexity to the electricity network.

To ensure reliable service, operators aim to meet stringent security standards. One such standard is N-1 security for medium-voltage networks, which ensures that electricity supply can continue even if a single connection fails.

This repository contains code of a feasibility study of quantum computing for optimising the robustness of energy networks in the event of cable failures, known as the 'N-1 problem'.

The N-1 problem arises in graph theory as a reconfigurational mathematical challenge. When a cut is introduced (or an edge is removed) in a given graph of nodes and edges, the N-1 problem seeks the most efficient method of reconfiguring the nodes with the fewest possible edges.

Results of the feasibility study can be found:

This project was carried out in collaboration with Alliander, a major Dutch grid operator. See also the Alliander Quantum technology GitHub page, which includes resources and updates related to their quantum research initiatives in energy grid optimization.

This work is supported by the Dutch National Growth Fund (NGF) as part of the Quantum Delta NL programme.

Documentation

Documentation and usage examples of the tno.quantum.problems.n_minus_1 package can be found here.

Install

Easily install the tno.quantum.problems.n_minus_1 package using pip:

$ python -m pip install tno.quantum.problems.n_minus_1

If you wish to run the tests you can use:

$ python -m pip install tno.quantum.problems.n_minus_1[tests]

Usage examples

  1. Gate-based solver.

    First create a network graph

    import networkx as nx
    
    graph = nx.Graph()
    
    # Active edges have weight 1
    active_edges = [(0, 1), (0, 2), (0, 3), (1, 4), (0, 5)]
    for node_n, node_m in active_edges:
        graph.add_edge(node_n, node_m, weight=1)
    
    # Inactive edges have weight 1
    inactive_edges = [(2, 4), (2, 3), (3, 4), (1, 2), (4, 5)]
    for node_n, node_m in inactive_edges:
        graph.add_edge(node_n, node_m, weight=-1)
    
    load_flow_correctness = [[(0, 2), (0, 5), (0, 3), (1, 4), (4, 5)]]
    
    # Failing edge
    failing_edge = (0, 1)            

    Network graph

    Use a Grover's algorithm to efficiently find required edge to toggle.

    from qiskit.visualization import plot_distribution
    
    from tno.quantum.problems.n_minus_1 import GateBasedNMinusOneSolver
    
    # Run the algorithm
    solver = GateBasedNMinusOneSolver(
        graph, failing_edge, load_flow_correctness, n_toggles=1, n_iter=1
    )
    executed_run = solver.run()
    
    # Plot the results
    counts = executed_run.result().results[0].data.counts
    labled_counts = {
        inactive_edge: counts[f"0x{i}"] for i, inactive_edge in enumerate(inactive_edges)
    }
    fig = plot_distribution(
        labled_counts,
        title="Failed Edge (0,1) - Correct Toggle (4,5)",
        bar_labels=True,
    )
    fig.show()

    Algorithm results

  2. Annealing based solver. Note: the example uses simulated annealing.

    from tno.quantum.problems.n_minus_1 import QABasedNMinusOneSolver
    from tno.quantum.problems.n_minus_1.quantum_annealing.datasets import load_small_dataset
    
    # Create network graph
    graph = load_small_dataset()
    graph.edges[1, 5]["I_max"] = 0
    failing_edge = (1, 4)
    
    # Create Solver
    solver_config = {
        "name": "simulated_annealing_solver",
        "options": {
            "num_reads": 100,
            "num_sweeps": 100000,
            "num_sweeps_per_beta": 100,
            "seed": 2024,
        },
    }
    solver = QABasedNMinusOneSolver(graph, failing_edge)
    
    # Perform experiment
    results = solver.run(solver_config=solver_config)
    print(results)

    Results the following output

    k | count |        turned on        |       turned off        
    --+-------+-------------------------+-------------------------
    1 |   4   |         (1, 5)          |
    1 |   1   |         (0, 1)          |
    3 |   1   |     (0, 1), (1, 5)      |         (5, 6)
    

(End)use limitations

The content of this software may solely be used for applications that comply with international export control laws.