///|
/// One FCFS exponential service center in an open Jackson network.
pub(all) struct JacksonNode {
service : Double
servers : Int
} derive(Debug, ToJson)
///|
/// Open single-class Jackson network. routing[i][j] is the probability that
/// a completion at node i next visits node j; missing row mass exits the system.
pub(all) struct JacksonNetwork {
nodes : Array[JacksonNode]
external : Array[Double]
routing : Array[Array[Double]]
} derive(Debug, ToJson)
///|
/// Stable traffic and M/M/c results for one network node.
pub(all) struct JacksonNodeMetrics {
arrival : Double
utilization : Double
delay_probability : Double
mean_queue : Double
mean_system : Double
mean_wait : Double
mean_response : Double
} derive(Debug, ToJson)
///|
/// Aggregate open-network result. Response time is per external customer,
/// including all internal visits and feedback.
pub(all) struct JacksonMetrics {
nodes : Array[JacksonNodeMetrics]
external_arrival : Double
total_mean_customers : Double
mean_external_response : Double
relative_flow_residual : Double
} derive(Debug, ToJson)
///|
fn magnitude(value : Double) -> Double {
if value < 0.0 {
-value
} else {
value
}
}
///|
fn JacksonNetwork::validate(self : JacksonNetwork) -> Unit raise QueueError {
let n = self.nodes.length()
if n == 0 || n > 128 {
raise Limit("Jackson network must contain 1..128 nodes")
}
if self.external.length() != n || self.routing.length() != n {
raise Invalid("external vector and routing matrix must match node count")
}
let mut external_total = 0.0
for i in 0.. 1.0 {
raise Invalid("routing row probabilities must sum to at most one")
}
}
rate(external_total, "total external arrival rate")
self.validate_exit_reachability()
}
///|
/// For a finite substochastic routing matrix, every state reaching a row with
/// positive exit probability implies eventual absorption from every node.
fn JacksonNetwork::validate_exit_reachability(
self : JacksonNetwork,
) -> Unit raise QueueError {
let n = self.nodes.length()
let can_exit = Array::make(n, false)
for i in 0.. 0.0 && can_exit[j] {
can_exit[i] = true
changed = true
}
j += 1
}
}
}
if !changed {
break
}
}
for i in 0.. Array[Double] raise QueueError {
self.validate()
let n = self.nodes.length()
let augmented : Array[Array[Double]] = Array::make(n, [])
for i in 0.. pivot_size {
pivot_size = candidate
pivot_row = r
}
}
if pivot_size <= 1.0e-14 {
raise Numerical("traffic equation is singular or ill-conditioned")
}
if pivot_row != col {
let temporary = augmented[col]
augmented[col] = augmented[pivot_row]
augmented[pivot_row] = temporary
}
let pivot = augmented[col][col]
for r in (col + 1).. 1.0e-9 {
raise Numerical("traffic equation residual exceeds tolerance")
}
traffic
}
///|
fn JacksonNetwork::traffic_residual(
self : JacksonNetwork,
traffic : Array[Double],
) -> Double {
let n = self.nodes.length()
let mut worst = 0.0
for i in 0.. worst {
worst = relative
}
}
worst
}
///|
/// Analyze an open Jackson network. Every node must have a path to exit and
/// each resulting M/M/c center must be strictly stable.
pub fn JacksonNetwork::analyze(
self : JacksonNetwork,
) -> JacksonMetrics raise QueueError {
let arrivals = self.traffic()
let n = self.nodes.length()
let node_metrics = Array::make(n, {
arrival: 0.0,
utilization: 0.0,
delay_probability: 0.0,
mean_queue: 0.0,
mean_system: 0.0,
mean_wait: 0.0,
mean_response: 0.0,
})
let mut total_customers = 0.0
for i in 0.. Array[Double] raise QueueError {
self.traffic()
}