Skip to main content
Version: 0.35.0

Tracing

Once you have a connected network model you can start following the connectivity to do some interesting analyses, from simple tasks such as adding up cable lengths along a line to advanced functions like a full load flow analysis.

The Evolve SDK provides an API to make following connectivity (what we will call tracing) less cumbersome and more productive. It provides a variety of use-case specific traces out of the box, but also a generic mechanism to write your own traces in an easy to use manner. The Tracing package is a great place to get an overview of the kind of tools we provide as part of the SDK.

Basic Connectivity

So, let's start at the absolute basic use case. "What is directly connected to this piece of equipment?". Obviously we provide a function to let you to find out. Let's see it in code:

from zepben.evolve import Junction
from zepben.evolve.tracing import ConnectivityResult, connected_equipment

switch: Breaker = network.get("aSwitch")
connectivity: List<ConnectivityResult> = switch.connected_equipment(PhaseCode.ABCN)
# or:
connectivity: List<ConnectivityResult> = connected_equipment(switch, PhaseCode.ABCN)
for cr in connectivity:
print(f"{cr.from_equip()} is connected to {it.to_equip()} via terminals {cr.from_terminal()} -> {it.to_terminal()} ",
'through phases {map(cr.nominal_phase_paths(), lambda path: f"{path.from_phase()}:{path.to_phase()}" })')

Sometimes you want to be a bit more specific about your connectivity, such as "what is connected only to one side of the equipment?". You can do this by using specific terminals:

from zepben.evolve import Breaker
from zepben.evolve.tracing import ConnectivityResult, connected_terminals

switch: Breaker = network.get("aSwitch")
connectivity: List<ConnectivityResult> = switch.connected_terminals(PhaseCode.ABCN)
# or
connectivity: List<ConnectivityResult> = connected_terminals(switch, PhaseCode.ABCN)

This will produce a subset of the result above, with just the equipment connected to the given terminal. The connectivity result instances in the subset will be exactly the same as the previous example.

Sometimes you do not care about the phase connectivity at all, and are just interested in terminals that share a connectivity node:

switch: Breaker = network.get("aSwitch")
for it in switch.get_terminal_by_sn(1).connected_terminals():
print(it)

This will return a Generator of all the terminals that share the same ConnectivityNode, regardless of any phase connectivity.

Tracing

Feeder Direction

UPSTREAM and DOWNSTREAM are concepts that only apply to equipment on feeders (HV or LV). These directions are purely for finding the feeder head, and have not been calculated with any power flow analysis. There is also a concept of BOTH if the feeder head can be found in either direction. e.g. in a loop or dual feed situation

Basic Traversal

To trace a network, we obviously need to repeat the above process for each piece of equipment in the connectivity result list. In computer science, stepping between nodes in a data structure like this is known as a traversal. Conveniently, the SDK provides a Traversal interface with a number of implementations also provided for different use cases.

The most common type you will use is the BasicTraversal. In a nutshell, this class lets you easily specify how to step to connected objects, add custom actions to perform at each step to an object, and conditions on which the tracing should stop. It has some more advanced features, such as allowing you to specify how to queue steps (for breadth, or depth or priority traversals) as well as custom tracking of objects. See the code docs for more details.

There are a number of traces which we have identified as being frequently required. So, the SDK provides a number of preconfigured BasicTraversal creators for these common use cases. These can be found in the tracing package.

To get started, lets take a look at the tracing.connected_equipment_trace(). This returns a BasicTraversal that is preconfigured to continuously step to equipment that is connected in any way. Under the covers it uses the Tracing.connected_equipment function we looked at above to continuously step to connected objects. Now, say we wanted to identify the nearest circuit breakers to a piece of equipment, capturing all equipment between those breakers. We can do that with the following:

from zepben.evolve import NetworkService, AcLineSegment, Breaker, ConductingEquipment
from zepben.evolve.tracing import connected_equipment_trace
network = NetworkService()

# Populate network service with your network

acls = network.get("my conductor", AcLineSegment)
if acls:
breakers = []
equipment_between_breakers = []
trace = connected_equipment_trace()
trace.add_stop_condition(lambda equip: isinstance(equip, Breaker))
trace.add_step_action(lambda equipment, stopping: breakers.append(equipment) if isinstance(equipment, Breaker) elif not stopping equipment_between_breakers.append(equipment))
await trace.run(acls)

print(f"The closest circuit breakers are: {breakers}")
print(f"The equipment between the above breakers: {equipment_between_breakers}")

Let's break down what is happening with the trace:

  • add_stop_condition is returning true if the equipment at the current step is a Breaker. This will make the trace stop at the current step if the equipment is a breaker. Note that this will not terminate the trace, it simply stops any more traversing from the current step. That is, no more equipment will be queued to be stepped to from the equipment at this step. If there are other paths / branches in the trace in the queue, they will still be stepped to.
  • add_step_action is capturing the equipment as a breaker if it is a breaker, otherwise it captures the equipment as between breakers equipment if it is not stopping (as stopping happens on the breaker).

As you can see this provides an extremely simple but powerful way to perform useful traces over your network.

Useful Traces

There are a number of traces that will be common between all networks. We have identified numerous of these and provide an easy way to instantiate them.

Set Phases Trace

traces.set_phases() returns a zepben.evolve.tracing.phasing.SetPhases instance. This provides a way to dynamically set phases at runtime based on a network's energy sources and the nominal phase connectivity within the network.

Phase Traces

The following traces all use phase based connectivity. This means that equipment that share connectivity nodes, but not phases, will not be considered connected, and when a phase is dropped, it will no longer be considered for future connectivity. Each of these traces will use different aspects of the network to limit the scope of the trace:

  • traces.phase_trace() will trace based on nominal phase connectivity, ignoring open switches or in service flags.
  • traces.normal_phase_trace() will trace based on nominal phase connectivity, stopping at normally open switches or equipment flagged as not normally in service.
  • traces.current_phase_trace() will trace based on nominal phase connectivity, stopping at currently open switches or equipment flagged as not currently in service.
  • traces.normal_downstream_trace() will trace downstream of the start equipment based on the normal state of the network.
  • traces.current_downstream_trace() will trace downstream of the start equipment based on the current state of the network.
  • traces.normal_upstream_trace() will trace upstream of the start equipment based on the normal state of the network.
  • traces.current_upstream_trace() will trace upstream of the start equipment based on the current state of the network.

Equipment Connectivity Traces

The following work on ConductingEquipment, and ignores phase connectivity, instead considering things to be connected if they share a ConnectivityNode:

  • traces.normal_downstream_equipment_trace() will trace in the downstream direction, stopping at normally open switches, or equipment flagged as not normally in service.
  • traces.current_downstream_equipment_trace() will trace in the downstream direction, stopping at currently open switches, or equipment flagged as not normally in service.
  • traces.normal_upstream_equipment_trace() will trace in the upstream direction, stopping at normally open switches, or equipment flagged as not normally in service.
  • traces.current_upstream_equipment_trace() will trace in the upstream direction, stopping at currently open switches, or equipment flagged as not normally in service.

Other Traces

There are other useful but less common traces available. See the code for all the available traces.