///|
/// Product-form center for a single-class, single-server closed network.
/// Fcfs centers model one exponential server; Delay centers have infinite
/// servers and no queue.
pub(all) enum ClosedCenter {
Fcfs(Double)
Delay(Double)
} derive(Debug, ToJson)
///|
/// Single-class closed queueing network. Visits are relative visits per
/// reference customer cycle; think_time is the per-customer delay-center time.
pub(all) struct ClosedNetwork {
centers : Array[ClosedCenter]
visits : Array[Double]
think_time : Double
} derive(Debug, ToJson)
///|
/// Per-center mean-value analysis at one population. offered_load is
/// throughput * visits * service time (Erlangs), not a service-time demand.
/// It is busy probability at Fcfs and mean busy servers at Delay.
pub(all) struct ClosedCenterMetrics {
mean_jobs : Double
mean_queue : Double
mean_wait : Double
mean_response : Double
offered_load : Double
} derive(Debug, ToJson)
///|
/// Population operating point; conservation_residual compares center jobs,
/// think population and the requested closed population.
pub(all) struct ClosedPopulationMetrics {
population : Int
throughput : Double
mean_network_response : Double
mean_cycle_time : Double
mean_think_population : Double
centers : Array[ClosedCenterMetrics]
conservation_residual : Double
} derive(Debug, ToJson)
///|
fn ClosedCenter::service_time(self : ClosedCenter) -> Double {
match self {
Fcfs(service) => service
Delay(service) => service
}
}
///|
fn ClosedCenter::is_fcfs(self : ClosedCenter) -> Bool {
match self {
Fcfs(_) => true
Delay(_) => false
}
}
///|
fn ClosedNetwork::validate(self : ClosedNetwork) -> Unit raise QueueError {
let n = self.centers.length()
if n == 0 || n > 128 || self.visits.length() != n {
raise Invalid("closed network needs 1..128 centers and matching visits")
}
nonnegative(self.think_time, "think time")
let mut has_visit = false
for i in 0.. 1.0e12 {
raise Limit("FCFS service time exceeds numerical range")
}
nonnegative(self.visits[i], "center visit ratio")
if self.visits[i] > 1.0e12 {
raise Limit("center visit ratio exceeds 1e12")
}
if self.visits[i] > 0.0 {
has_visit = true
}
}
if !has_visit {
raise Invalid("at least one center must have a positive visit ratio")
}
}
///|
/// Exact single-class MVA population curve for FCFS single-server and delay
/// centers. Multiserver FCFS centers are deliberately not approximated here.
pub fn ClosedNetwork::population_curve(
self : ClosedNetwork,
maximum_population : Int,
) -> Array[ClosedPopulationMetrics] raise QueueError {
self.validate()
if maximum_population < 0 || maximum_population > 1000 {
raise Limit("closed MVA population bound must be 0..1000")
}
let center_count = self.centers.length()
let output = []
let zero_centers = Array::make(center_count, {
mean_jobs: 0.0,
mean_queue: 0.0,
mean_wait: 0.0,
mean_response: 0.0,
offered_load: 0.0,
})
output.push({
population: 0,
throughput: 0.0,
mean_network_response: 0.0,
mean_cycle_time: 0.0,
mean_think_population: 0.0,
centers: zero_centers,
conservation_residual: 0.0,
})
let previous_jobs = Array::make(center_count, 0.0)
for population in 1..<=maximum_population {
let response_per_visit = Array::make(center_count, 0.0)
let mut denominator = self.think_time
for i in 0.. 1.0e-8 * (1.0 + population.to_double()) {
raise Numerical("closed MVA population conservation failed")
}
output.push({
population,
throughput,
mean_network_response: network_response,
mean_cycle_time: cycle_time,
mean_think_population: think_population,
centers: center_metrics,
conservation_residual: residual,
})
}
output
}
///|
/// Analyze one requested population while retaining the exact MVA curve used
/// for its recurrence.
pub fn ClosedNetwork::analyze_population(
self : ClosedNetwork,
population : Int,
) -> ClosedPopulationMetrics raise QueueError {
let curve = self.population_curve(population)
curve[population]
}