tno.quantum.problems.n_minus_1 1.0.0
N-1 quantum based solver 
License: Apache License, Version 2.0
Requires Python: >=3.9
Keywords: TNO, Quantum, N-1 problem, QUBO, Quantum Annealing, Gate based

# 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: 

- [Neumann et al., - Quantum algorithms for N-1 security in power grids (May 2024)](https://arxiv.org/html/2405.00434v1)


This project was carried out in collaboration with Alliander, a major Dutch grid operator. See also the [Alliander Quantum technology GitHub page](https://github.com/alliander-opensource/quantum_research), 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](https://tno-quantum.github.io/documentation/).

## Install

Easily install the `tno.quantum.problems.n_minus_1` package using pip:

```console
$ python -m pip install tno.quantum.problems.n_minus_1
```

If you wish to run the tests you can use:
```console
$ python -m pip install tno.quantum.problems.n_minus_1[tests]
```

## Usage examples

1. Gate-based solver.

    First create a network graph

    ```python
    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](images/graph.png)

    Use a Grover's algorithm to efficiently find required edge to toggle. 

    ```python
    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](images/grover_n-1.png)


2. Annealing based solver. *Note: the example uses simulated annealing.*

    ```python
    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.