All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zepben.evolve.services.network.tracing.feeder.SetDirection.kt Maven / Gradle / Ivy

There is a newer version: 0.24.0rc1
Show newest version
/*
 * Copyright 2022 Zeppelin Bend Pty Ltd
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.zepben.evolve.services.network.tracing.feeder

import com.zepben.evolve.cim.iec61970.base.core.ConductingEquipment
import com.zepben.evolve.cim.iec61970.base.core.Feeder
import com.zepben.evolve.cim.iec61970.base.core.Terminal
import com.zepben.evolve.cim.iec61970.base.wires.PowerTransformer
import com.zepben.evolve.cim.iec61970.base.wires.Switch
import com.zepben.evolve.services.network.NetworkService
import com.zepben.evolve.services.network.tracing.OpenTest
import com.zepben.evolve.services.network.tracing.traversals.BasicTracker
import com.zepben.evolve.services.network.tracing.traversals.BranchRecursiveTraversal
import com.zepben.evolve.services.network.tracing.traversals.WeightedPriorityQueue

/**
 * Convenience class that provides methods for setting feeder direction on a [NetworkService]
 * This class is backed by a [BranchRecursiveTraversal].
 */
class SetDirection {

    /**
     * The [BranchRecursiveTraversal] used when tracing the normal state of the network.
     *
     * NOTE: If you add stop conditions to this traversal it may no longer work correctly, use at your own risk.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val normalTraversal: BranchRecursiveTraversal = BranchRecursiveTraversal(
        { terminal, traversal -> setDownstreamAndQueueNext(traversal, terminal, OpenTest.NORMALLY_OPEN, DirectionSelector.NORMAL_DIRECTION) },
        { WeightedPriorityQueue.processQueue { it.phases.numPhases() } },
        { BasicTracker() },
        { WeightedPriorityQueue.branchQueue { it.phases.numPhases() } }
    )

    /**
     * The [BranchRecursiveTraversal] used when tracing the current state of the network.
     *
     * NOTE: If you add stop conditions to this traversal it may no longer work correctly, use at your own risk.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val currentTraversal: BranchRecursiveTraversal = BranchRecursiveTraversal(
        { terminal, traversal -> setDownstreamAndQueueNext(traversal, terminal, OpenTest.CURRENTLY_OPEN, DirectionSelector.CURRENT_DIRECTION) },
        { WeightedPriorityQueue.processQueue { it.phases.numPhases() } },
        { BasicTracker() },
        { WeightedPriorityQueue.branchQueue { it.phases.numPhases() } }
    )

    /**
     * Apply feeder directions from all feeder head terminals in the network.
     *
     * @param network The network in which to apply feeder directions.
     */
    fun run(network: NetworkService) {
        run(network.sequenceOf().mapNotNull { it.normalHeadTerminal }.filter { !it.conductingEquipment.isNormallyOpenSwitch() }.toList())
    }

    /**
     * Apply [FeederDirection.DOWNSTREAM] from the [terminal].
     *
     * @param terminal The terminal to start applying feeder direction from.
     */
    fun run(terminal: Terminal) {
        run(listOf(terminal))
    }

    private fun run(startTerminals: List) {
        normalTraversal.tracker.clear()
        currentTraversal.tracker.clear()

        startTerminals.forEach {
            normalTraversal.reset().run(it)
            currentTraversal.reset().run(it)
        }
    }

    private fun setDownstreamAndQueueNext(
        traversal: BranchRecursiveTraversal,
        terminal: Terminal,
        openTest: OpenTest,
        directionSelector: DirectionSelector
    ) {
        val direction = directionSelector.select(terminal)
        if (!direction.add(FeederDirection.DOWNSTREAM))
            return

        val connected = terminal.connectivityNode?.let { cn -> cn.terminals.filter { it != terminal } } ?: emptyList()
        val processor = ::flowUpstreamAndQueueNextStraight.takeIf { connected.size == 1 } ?: ::flowUpstreamAndQueueNextBranch

        connected.forEach {
            processor(traversal, it, openTest, directionSelector)
        }
    }

    private fun isFeederHeadTerminal(terminal: Terminal): Boolean =
        terminal.conductingEquipment?.run {
            containers
                .asSequence()
                .filterIsInstance()
                .any { it.normalHeadTerminal == terminal }
        } ?: false

    private fun reachedSubstationTransformer(terminal: Terminal): Boolean =
        terminal.conductingEquipment.let { ce -> (ce is PowerTransformer) && ce.substations.isNotEmpty() }

    private fun flowUpstreamAndQueueNextStraight(
        traversal: BranchRecursiveTraversal,
        terminal: Terminal,
        openTest: OpenTest,
        directionSelector: DirectionSelector
    ) {
        if (!traversal.tracker.visit(terminal))
            return

        if (terminal.conductingEquipment?.numTerminals() == 2)
            flowUpstreamAndQueueNext(terminal, openTest, directionSelector, traversal.queue::add)
        else
            flowUpstreamAndQueueNext(terminal, openTest, directionSelector) { traversal.startNewBranch(it) }
    }

    private fun flowUpstreamAndQueueNextBranch(
        traversal: BranchRecursiveTraversal,
        terminal: Terminal,
        openTest: OpenTest,
        directionSelector: DirectionSelector
    ) {
        // We don't want to visit the upstream terminal if we have branched as it prevents the downstream path of a loop processing correctly, but we
        // still need to make sure we don't re-visit the upstream terminal.
        if (traversal.hasVisited(terminal))
            return

        flowUpstreamAndQueueNext(terminal, openTest, directionSelector) { traversal.startNewBranch(it) }
    }

    private fun flowUpstreamAndQueueNext(
        terminal: Terminal,
        openTest: OpenTest,
        directionSelector: DirectionSelector,
        queue: (Terminal) -> Unit
    ) {
        val direction = directionSelector.select(terminal)
        if (!direction.add(FeederDirection.UPSTREAM))
            return

        if (isFeederHeadTerminal(terminal) || reachedSubstationTransformer(terminal))
            return

        val ce = terminal.conductingEquipment ?: return
        if (openTest.isOpen(ce, null))
            return

        ce.terminals
            .asSequence()
            .filter { it != terminal }
            .forEach { queue(it) }
    }

    private fun ConductingEquipment?.isNormallyOpenSwitch(): Boolean =
        (this is Switch) && isNormallyOpen()

    private fun BranchRecursiveTraversal.startNewBranch(terminal: Terminal) {
        branchQueue.add(branchSupplier().setStart(terminal))
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy