///|
pub(all) struct UtilityNode {
name : String
demand : Double
capacity : Double
priority : Int
available : Bool
} derive(Debug, Eq, ToJson)
///|
pub(all) struct UtilityNetwork {
kind : String
nodes : Array[UtilityNode]
total_demand : Double
total_capacity : Double
utilization : Double
feasible : Bool
curtailed_demand : Double
} derive(Debug, Eq, ToJson)
///|
pub(all) struct UtilityAllocation {
node : String
requested : Double
allocated : Double
curtailed : Double
fraction : Double
served : Bool
} derive(Debug, Eq, ToJson)
///|
pub fn utility_node(
name : String,
demand : Double,
capacity : Double,
priority : Int,
) -> UtilityNode {
{ name, demand, capacity, priority, available: capacity > 0.0 }
}
///|
pub fn build_utility_network(
kind : String,
nodes : ArrayView[UtilityNode],
) -> UtilityNetwork {
let mut demand = 0.0
let mut capacity = 0.0
for node in nodes {
demand += node.demand.max(0.0)
capacity += node.capacity.max(0.0)
}
{
kind,
nodes: nodes.to_owned(),
total_demand: demand,
total_capacity: capacity,
utilization: if capacity == 0.0 {
0.0
} else {
demand / capacity
},
feasible: demand <= capacity,
curtailed_demand: (demand - capacity).max(0.0),
}
}
///|
pub fn network_utilization(network : UtilityNetwork) -> Double {
network.utilization
}
///|
pub fn network_margin(network : UtilityNetwork) -> Double {
network.total_capacity - network.total_demand
}
///|
pub fn network_is_feasible(
network : UtilityNetwork,
reserve_fraction : Double,
) -> Bool {
network.total_demand <= network.total_capacity * (1.0 - reserve_fraction)
}
///|
pub fn network_reserve(network : UtilityNetwork) -> Double {
if network.total_capacity == 0.0 {
0.0
} else {
network_margin(network) / network.total_capacity
}
}
///|
pub fn allocate_utility(network : UtilityNetwork) -> Array[UtilityAllocation] {
let indices = [ for index in 0.. index ]
for first in 0..= requested,
})
}
result
}
///|
pub fn allocations_total(allocations : ArrayView[UtilityAllocation]) -> Double {
allocations.fold(init=0.0, (sum, allocation) => sum + allocation.allocated)
}
///|
pub fn allocations_curtailed(
allocations : ArrayView[UtilityAllocation],
) -> Double {
allocations.fold(init=0.0, (sum, allocation) => sum + allocation.curtailed)
}
///|
pub fn allocations_service_rate(
allocations : ArrayView[UtilityAllocation],
) -> Double {
let mut requested = 0.0
let mut allocated = 0.0
for allocation in allocations {
requested += allocation.requested
allocated += allocation.allocated
}
if requested == 0.0 {
1.0
} else {
allocated / requested
}
}
///|
pub fn allocations_failed_nodes(
allocations : ArrayView[UtilityAllocation],
) -> Array[String] {
let result = []
for allocation in allocations {
if !allocation.served {
result.push(allocation.node)
}
}
result
}
///|
pub fn network_add_node(
network : UtilityNetwork,
node : UtilityNode,
) -> UtilityNetwork {
let nodes = network.nodes.copy()
nodes.push(node)
build_utility_network(network.kind, nodes)
}
///|
pub fn network_remove_node(
network : UtilityNetwork,
name : String,
) -> UtilityNetwork {
let nodes = []
for node in network.nodes {
if node.name != name {
nodes.push(node)
}
}
build_utility_network(network.kind, nodes)
}
///|
pub fn network_scale_demand(
network : UtilityNetwork,
factor : Double,
) -> UtilityNetwork {
let nodes = [
for node in network.nodes => {
{
name: node.name,
demand: node.demand * factor,
capacity: node.capacity,
priority: node.priority,
available: node.available,
}
}
]
build_utility_network(network.kind, nodes)
}
///|
pub fn network_scale_capacity(
network : UtilityNetwork,
factor : Double,
) -> UtilityNetwork {
let nodes = [
for node in network.nodes => {
{
name: node.name,
demand: node.demand,
capacity: node.capacity * factor,
priority: node.priority,
available: node.available && factor > 0.0,
}
}
]
build_utility_network(network.kind, nodes)
}
///|
pub fn network_sensitivity(
network : UtilityNetwork,
demand_factors : ArrayView[Double],
) -> Array[Double] {
let result = []
for factor in demand_factors {
result.push(network_scale_demand(network, factor).utilization)
}
result
}
///|
pub fn utility_allocation_csv(
allocations : ArrayView[UtilityAllocation],
) -> String {
let lines = ["node,requested,allocated,curtailed,fraction,served"]
for allocation in allocations {
lines.push(
allocation.node +
"," +
allocation.requested.to_string() +
"," +
allocation.allocated.to_string() +
"," +
allocation.curtailed.to_string() +
"," +
allocation.fraction.to_string() +
"," +
allocation.served.to_string(),
)
}
lines.join("\n")
}
///|
pub fn utility_network_markdown(network : UtilityNetwork) -> String {
let lines = [
"# Utility network: " + network.kind,
"",
"| Node | Demand | Capacity | Priority | Available |",
"|---|---:|---:|---:|---|",
]
for node in network.nodes {
lines.push(
"| " +
node.name +
" | " +
node.demand.to_string() +
" | " +
node.capacity.to_string() +
" | " +
node.priority.to_string() +
" | " +
node.available.to_string() +
" |",
)
}
lines.push("")
lines.push("Utilization: " + network.utilization.to_string())
lines.push("Feasible: " + network.feasible.to_string())
lines.join("\n")
}
///|
pub fn utility_network_summary(network : UtilityNetwork) -> String {
"kind=" +
network.kind +
",nodes=" +
network.nodes.length().to_string() +
",demand=" +
network.total_demand.to_string() +
",capacity=" +
network.total_capacity.to_string() +
",utilization=" +
network.utilization.to_string() +
",curtailed=" +
network.curtailed_demand.to_string()
}
///|
pub fn utility_node_rank(network : UtilityNetwork, name : String) -> Int {
for node in network.nodes {
if node.name == name {
return node.priority
}
}
-1
}
///|
pub fn utility_network_max_priority(network : UtilityNetwork) -> Int {
let mut result = 0
for node in network.nodes {
result = result.max(node.priority)
}
result
}
///|
pub fn utility_network_min_priority(network : UtilityNetwork) -> Int {
if network.nodes.length() == 0 {
return 0
}
let mut result = network.nodes[0].priority
for node in network.nodes {
result = result.min(node.priority)
}
result
}
///|
pub fn utility_node_count_available(network : UtilityNetwork) -> Int {
let mut count = 0
for node in network.nodes {
if node.available {
count += 1
}
}
count
}
///|
pub fn utility_network_alerts(
network : UtilityNetwork,
maximum_utilization : Double,
) -> Array[String] {
let alerts = []
if network.utilization > maximum_utilization {
alerts.push("network utilization exceeds limit")
}
if network.curtailed_demand > 0.0 {
alerts.push("utility demand is curtailed")
}
if utility_node_count_available(network) < network.nodes.length() {
alerts.push("one or more utility sources are unavailable")
}
alerts
}
///|
pub fn utility_network_balanced(
network : UtilityNetwork,
tolerance : Double,
) -> Bool {
if network.nodes.length() == 0 {
return true
}
let average = network.total_capacity / network.nodes.length().to_double()
for node in network.nodes {
if (node.capacity - average).abs() > tolerance * average.max(1.0) {
return false
}
}
true
}
///|
pub fn utility_network_cost(
network : UtilityNetwork,
unit_cost : Double,
) -> Double {
network.total_demand * unit_cost
}
///|
pub fn utility_network_emissions(
network : UtilityNetwork,
emission_factor : Double,
) -> Double {
network.total_demand * emission_factor
}