Complete Example: Building a Distribution System#
This example demonstrates the full NREL-shift workflow — from fetching geospatial data to producing a simulator-ready distribution system model. Each step links to its detailed guide for further reading.
Overview#
Step |
Action |
Guide |
|---|---|---|
1 |
Fetch building parcels from OpenStreetMap |
|
2 |
Cluster parcels and build a distribution graph |
|
3 |
Update branch types for your equipment catalog |
|
4 |
Assign phases to transformer secondaries |
|
5 |
Assign voltage levels |
|
6 |
Map equipment to nodes and edges |
|
7 |
Assemble and export the distribution system |
Step 1: Import Modules#
from pathlib import Path
from shift import (
parcels_from_location,
get_kmeans_clusters,
PRSG,
DistributionGraph,
DistributionSystemBuilder,
BalancedPhaseMapper,
TransformerVoltageMapper,
EdgeEquipmentMapper,
TransformerPhaseMapperModel,
TransformerVoltageModel,
TransformerTypes,
GeoLocation,
PlotManager,
add_parcels_to_plot,
add_distribution_graph_to_plot,
)
from gdm import (
DistributionSystem,
DistributionTransformer,
DistributionBranchBase,
MatrixImpedanceBranch,
)
from gdm.quantities import Voltage, ApparentPower
from infrasys.quantities import Distance
import osmnx as ox
Step 2: Fetch Parcels#
location = "Fort Worth, TX"
search_distance = Distance(500, "m")
parcels = parcels_from_location(location, search_distance)
print(f"Found {len(parcels)} parcels")
Step 3: Build the Distribution Graph#
def _get_parcel_points(parcels):
return [
p.geometry[0] if isinstance(p.geometry, list) else p.geometry
for p in parcels
]
num_clusters = max(len(parcels) // 2, 1)
clusters = get_kmeans_clusters(num_clusters, _get_parcel_points(parcels))
builder = PRSG(
groups=clusters,
source_location=GeoLocation(-97.3, 32.75),
)
graph = builder.get_distribution_graph()
print(f"Graph: {len(list(graph.get_nodes()))} nodes, {len(list(graph.get_edges()))} edges")
Step 4: Update Branch Types#
Replace generic DistributionBranchBase edges with the type expected by your equipment catalog:
new_graph = DistributionGraph()
for node in graph.get_nodes():
new_graph.add_node(node)
for from_node, to_node, edge in graph.get_edges():
if edge.edge_type == DistributionBranchBase:
edge.edge_type = MatrixImpedanceBranch
new_graph.add_edge(from_node, to_node, edge_data=edge)
Step 5: Map Phases#
transformer_phase_models = [
TransformerPhaseMapperModel(
tr_name=edge.name,
tr_type=TransformerTypes.SPLIT_PHASE,
tr_capacity=ApparentPower(25, "kilovoltampere"),
location=new_graph.get_node(from_node).location,
)
for from_node, _, edge in new_graph.get_edges()
if edge.edge_type is DistributionTransformer
]
phase_mapper = BalancedPhaseMapper(new_graph, mapper=transformer_phase_models, method="agglomerative")
Step 6: Map Voltages#
voltage_mapper = TransformerVoltageMapper(
new_graph,
xfmr_voltage=[
TransformerVoltageModel(
name=edge.name,
voltages=[Voltage(7.2, "kilovolt"), Voltage(120, "volt")],
)
for _, _, edge in new_graph.get_edges()
if edge.edge_type is DistributionTransformer
],
)
Step 7: Map Equipment#
Load an equipment catalog and create the equipment mapper. For a custom node-level mapper (e.g., area-based loads), see Mapping Equipment.
import shift
MODELS_FOLDER = Path(shift.__file__).parent.parent.parent / "tests" / "models"
catalog_sys = DistributionSystem.from_json(MODELS_FOLDER / "p1rhs7_1247.json")
eq_mapper = EdgeEquipmentMapper(new_graph, catalog_sys, voltage_mapper, phase_mapper)
Step 8: Build the System#
system_builder = DistributionSystemBuilder(
name="fort_worth_feeder",
dist_graph=new_graph,
phase_mapper=phase_mapper,
voltage_mapper=voltage_mapper,
equipment_mapper=eq_mapper,
)
system = system_builder.get_system()
print(f"Built system: {system.name}")
Step 9: Export#
output = Path("./models")
output.mkdir(exist_ok=True)
system.to_json(output / "fort_worth_feeder.json")
Optional: Visualize#
center = GeoLocation(*reversed(ox.geocode("Fort Worth, TX")))
plot_manager = PlotManager(center=center)
add_parcels_to_plot(parcels, plot_manager)
add_distribution_graph_to_plot(new_graph, plot_manager)
plot_manager.show()
Tips#
Start small — use a short search distance (200–500 m) when testing a new area.
Validate data — not all locations have good OpenStreetMap building coverage.
Equipment sizing — ensure transformer capacities in the catalog match your load assumptions.
Phase balance —
BalancedPhaseMapperwithmethod="greedy"works well for residential feeders.Error handling — wrap
parcels_from_locationcalls intry/exceptin case the geocoder or Overpass API is unavailable.
Troubleshooting#
Problem |
Solution |
|---|---|
No parcels found |
Increase |
Graph is disconnected |
Ensure the |
Equipment mapping errors |
Verify that branch types match the catalog; see Updating Branch Types |