Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GridOPTICS/GridPACK/llms.txt

Use this file to discover all available pages before exploring further.

In GridPACK, a power grid is represented as a graph: buses are the nodes and branches (transmission lines, transformers) are the edges. This graph is stored in a BaseNetwork<Bus, Branch> template object that distributes the graph across MPI ranks, tracks which buses and branches are owned locally versus mirrored as ghosts from neighboring ranks, and provides the partitioner that minimizes inter-rank communication. Understanding the network model is the first step to writing any GridPACK application.

Buses and branches

A bus corresponds to a physical bus bar or node in the power grid — a point where equipment connects. A branch corresponds to a transmission line or transformer connecting exactly two buses. Each bus and branch has:
  • An original index — the integer label from the input file (PSS/E .raw or similar). These are not required to be consecutive.
  • A global index — a contiguous index assigned by the framework after partitioning.
  • A local index — a process-local index in the range [0, numBuses()-1] used to address the bus or branch on a given rank.
  • An associated DataCollection object holding key-value pairs read from the input file.
  • An application-supplied component object (your Bus or Branch class) attached to the topology entry.

The BaseNetwork<Bus, Branch> template class

Networks are declared by specializing BaseNetwork with application-specific bus and branch types:
#include "gridpack/include/gridpack.hpp"

namespace myapp {

// Forward declarations of application component classes
class MyBus;
class MyBranch;

// Network type alias — conventional in GridPACK applications
typedef gridpack::network::BaseNetwork<MyBus, MyBranch> MyNetwork;

} // namespace myapp
The network constructor requires a Communicator that defines the set of processors the network lives on:
gridpack::parallel::Communicator comm; // defaults to MPI_COMM_WORLD
boost::shared_ptr<myapp::MyNetwork> network(new myapp::MyNetwork(comm));
The resulting object is an empty shell. Buses and branches are added by passing the network to a parser.

Key network queries

// Counts (include ghost buses/branches on this rank)
int n_buses    = network->numBuses();
int n_branches = network->numBranches();

// Counts (active only — no ghosts)
int total_buses    = network->totalBuses();
int total_branches = network->totalBranches();

// Access component objects by local index
boost::shared_ptr<myapp::MyBus>    bus    = network->getBus(i);
boost::shared_ptr<myapp::MyBranch> branch = network->getBranch(j);

// Access DataCollection by local index
boost::shared_ptr<gridpack::component::DataCollection> dc =
    network->getBusData(i);

// Active status — false means ghost
bool is_active = network->getActiveBus(i);

// Recover original bus index (from input file) for output
int orig = network->getOriginalBusIndex(i);

// Recover communicator (useful for multi-level parallelism)
const gridpack::parallel::Communicator &c = network->communicator();

Ghost buses and branches

When the network is partitioned, each rank owns a contiguous subgraph. However, buses and branches that are connected to locally-owned components but live on a neighboring rank must still be accessible for physics calculations. The partitioner creates ghost copies of these remote components on the local rank. Ghost buses and branches are read-only mirrors. Their data is kept current by periodic update operations that copy values from the active (home) rank to all ranks holding a ghost copy:
// Initialize exchange data structures (done once after partition)
network->initBusUpdate();
network->initBranchUpdate();

// ... inside the solve loop, after updating component state:
network->updateBuses();    // sync ghost bus fields
network->updateBranches(); // sync ghost branch fields
getActiveBus(i) returns false for ghost buses. Application code that loops over all local buses typically skips ghosts for output or accumulation:
for (int i = 0; i < network->numBuses(); i++) {
    if (!network->getActiveBus(i)) continue;
    // process active bus i
}
Bus and branch exchange buffers must be allocated before calling initBusUpdate() or initBranchUpdate(). This is done by calling factory.setExchange() after network->partition(). See the factory pattern for the full initialization sequence.

Network partitioning

Partitioning splits the graph across MPI ranks to balance computational load while minimizing the number of branches that cross rank boundaries (since each such crossing creates a ghost and requires communication). GridPACK uses an embedded graph partitioner (based on PTScotch or similar) accessible via:
network->partition();
This call should be made immediately after the parser populates the network, before any other setup operations. It redistributes buses and branches across ranks and adds the necessary ghost entries.

Finding components by original index

After partitioning, the local index of a component changes. To locate a bus by its original (input file) index, use:
// Returns a vector because a ghost copy may also exist on this rank
std::vector<int> local_indices = network->getLocalBusIndices(orig_bus_idx);

// For a branch, specify original indices of its two endpoint buses
std::vector<int> branch_indices =
    network->getLocalBranchIndices(orig_bus1_idx, orig_bus2_idx);
This is commonly used in contingency analysis, where a fault is specified by original bus or branch indices.

Data flow: parser → network → factory → mapper → solver

A full GridPACK solve follows this pipeline:
1

Parse input file

A parser (e.g., gridpack::powerflow::PTIParser) reads the network configuration file, creates buses and branches, and fills each component’s DataCollection object with raw parameters.
2

Partition the network

network->partition() distributes the graph across ranks and creates ghost entries at partition boundaries.
3

Initialize components via factory

factory.setComponents() pushes topology (neighbor lists) into bus/branch objects. factory.setExchange() allocates exchange buffers. factory.load() calls each component’s load(data) method to transfer values from DataCollection into component fields.
4

Assemble matrix and vector

After setting the calculation mode with factory.setMode(YBus), a FullMatrixMap<MyNetwork> loops over all buses and branches, calls their MatVecInterface methods, and assembles a distributed sparse matrix.
5

Solve and extract results

A LinearSolver or NonlinearSolver operates on the assembled matrix and RHS vector. A BusVectorMap can then push solution values back into bus components via setValues().

Declaring a network — complete example

#include "gridpack/include/gridpack.hpp"
#include "my_bus.hpp"
#include "my_branch.hpp"

typedef gridpack::network::BaseNetwork<MyBus, MyBranch> MyNetwork;

int main(int argc, char **argv) {
    gridpack::Environment env(argc, argv);
    gridpack::parallel::Communicator comm;

    boost::shared_ptr<MyNetwork> network(new MyNetwork(comm));

    // Populate network from a PSS/E v33 .raw file
    gridpack::parser::PTI33parser<MyNetwork> parser(network, "case.raw");
    parser.parse();

    // Partition across available MPI ranks
    network->partition();

    // Initialize factory (sets neighbors, exchange buffers, loads data)
    MyFactory factory(network);
    factory.setComponents();
    factory.setExchange();
    factory.load();

    // Set up ghost exchange for bus voltages
    network->initBusUpdate();

    // ... proceed to matrix assembly and solve
}
Always call network->partition() before factory.setComponents(). The component neighbor lists are derived from the partitioned topology. Calling setComponents() on an unpartitioned network produces incorrect results.

Framework overview

The four-layer architecture and factory pattern

Bus and branch components

How components implement physics via MatVecInterface

Build docs developers (and LLMs) love