1%% Closed Queueing Network: CTMC (SPN) vs NC Solver Comparison
3% This example demonstrates:
4% - Single-
class closed queueing network (product-form)
5% - Solves the same model two different ways:
6% 1. Traditional QN approach: solved with NC (Normalizing Constant) solver
7% 2. SPN approach: same network modeled as Petri Net, solved with CTMC
8% - Verifies that both approaches give identical results
9% - Demonstrates CTMC
's capability on Petri Net models
11% Network Configuration:
12% - Population: N=3 jobs circulating continuously
13% - Station 1: Queue (FCFS, μ₁=1.0)
14% - Station 2: Queue (FCFS, μ₂=0.8)
15% - Station 3: Delay (think time, mean=2.0)
16% - Routing: 1 → 2 → 3 → 1 (cycle)
18% This is product-form under Jackson's theorem:
20% - Exponential service times
22% - Product-form solution: π(n₁,n₂,n₃) = π₁(n₁)π₂(n₂)π₃(n₃)
26fprintf(
'========================================================================\n');
27fprintf(
'Closed QN: CTMC (SPN) vs NC Solver Comparison\n');
28fprintf(
'========================================================================\n');
32% =========================================================================
33% Part 1: Traditional Closed QN (Solved with NC Solver)
34% =========================================================================
35fprintf(
'\n========================================================================\n');
36fprintf(
'Approach 1: Traditional Closed QN (Solved with NC Solver)\n');
37fprintf(
'========================================================================\n');
39model_qn = Network(
'closed_qn_traditional');
42queue1 = Queue(model_qn,
'queue1', SchedStrategy.FCFS);
43queue2 = Queue(model_qn,
'queue2', SchedStrategy.FCFS);
44delay = Delay(model_qn,
'delay');
46% Define single job
class (closed)
47jobclass = ClosedClass(model_qn,
'jobs', 3, queue1); % 3 jobs, starting at queue1
49% Define service processes
50queue1.setService(
jobclass, Exp.fit_mean(1.0)); % μ₁ = 1.0
51queue2.setService(
jobclass, Exp.fit_mean(1/0.8)); % μ₂ = 0.8
52delay.setService(
jobclass, Exp.fit_mean(2.0)); % think time
54% Define routing: queue1 → queue2 → delay → queue1
55R_qn = model_qn.init_routing_matrix();
62% Solve with NC (Normalizing Constant) solver
63fprintf(
'\nSolving with NC (Normalizing Constant) solver...\n');
64solver_nc = SolverNC(model_qn);
65result_nc = solver_nc.getAvgTable();
67fprintf(
'NC Results:\n');
70% =========================================================================
71% Part 2: Same Network as SPN (Solved with CTMC)
72% =========================================================================
73fprintf(
'\n========================================================================\n');
74fprintf(
'Approach 2: Same Network as SPN (Solved with CTMC)\n');
75fprintf(
'========================================================================\n');
77model_spn = Network(
'closed_qn_spn');
79% SPN places represent station buffers
80p1 = Place(model_spn,
'p1'); % Queue 1 buffer
81p2 = Place(model_spn,
'p2'); % Queue 2 buffer
82p3 = Place(model_spn,
'p3'); % Delay buffer
84% Transitions represent service completions
85t1 = Transition(model_spn,
't1'); % Queue 1 service completion
86t2 = Transition(model_spn,
't2'); % Queue 2 service completion
87t3 = Transition(model_spn,
't3'); % Delay completion
89% Closed job
class with 3 jobs initially at p1
90jobclass_spn = ClosedClass(model_spn,
'jobs', 3, p1);
92% =========================================================================
93% Configure Transition t1: Service at Queue 1 (μ₁=1.0)
94% =========================================================================
95mode_t1 = t1.add_mode(
'service1');
96t1.set_distribution(mode_t1, Exp.fit_mean(1.0));
97t1.set_enabling_conditions(mode_t1, jobclass_spn, p1, 1); % Require job in p1
98t1.set_firing_outcome(mode_t1, jobclass_spn, p1, -1); % Remove from p1
99t1.set_firing_outcome(mode_t1, jobclass_spn, p2, 1); % Add to p2
101% =========================================================================
102% Configure Transition t2: Service at Queue 2 (μ₂=0.8)
103% =========================================================================
104mode_t2 = t2.add_mode(
'service2');
105t2.set_distribution(mode_t2, Exp.fit_mean(1/0.8));
106t2.set_enabling_conditions(mode_t2, jobclass_spn, p2, 1); % Require job in p2
107t2.set_firing_outcome(mode_t2, jobclass_spn, p2, -1); % Remove from p2
108t2.set_firing_outcome(mode_t2, jobclass_spn, p3, 1); % Add to p3
110% =========================================================================
111% Configure Transition t3: Think time at Delay (mean=2.0)
112% =========================================================================
113mode_t3 = t3.add_mode(
'think');
114t3.set_distribution(mode_t3, Exp.fit_mean(2.0));
115t3.set_enabling_conditions(mode_t3, jobclass_spn, p3, 1); % Require job in p3
116t3.set_firing_outcome(mode_t3, jobclass_spn, p3, -1); % Remove from p3
117t3.set_firing_outcome(mode_t3, jobclass_spn, p1, 1); % Return to p1
119% =========================================================================
121% =========================================================================
122R_spn = model_spn.init_routing_matrix();
125R_spn.set(jobclass_spn, jobclass_spn, p1, t1, 1.0);
126R_spn.set(jobclass_spn, jobclass_spn, t1, p2, 1.0);
129R_spn.set(jobclass_spn, jobclass_spn, p2, t2, 1.0);
130R_spn.set(jobclass_spn, jobclass_spn, t2, p3, 1.0);
133R_spn.set(jobclass_spn, jobclass_spn, p3, t3, 1.0);
134R_spn.set(jobclass_spn, jobclass_spn, t3, p1, 1.0);
136model_spn.link(R_spn);
138% Set initial state: all 3 jobs at p1
144fprintf(
'\nSolving with CTMC solver...\n');
145solver_ctmc = SolverCTMC(model_spn);
146result_ctmc = solver_ctmc.getAvgTable();
148fprintf(
'CTMC Results:\n');
151% =========================================================================
153% =========================================================================
154fprintf(
'\n========================================================================\n');
155fprintf(
'COMPARISON: NC Solver (Traditional) vs CTMC (SPN)\n');
156fprintf(
'========================================================================\n');
158% Map station names: queue1→p1, queue2→p2, delay→p3
159fprintf(
'\nDetailed Metrics Comparison:\n');
160fprintf(
'------------------------------------------------------------------------\n');
162stations_nc = {
'queue1',
'queue2',
'delay'};
163stations_spn = {
'p1',
'p2',
'p3'};
165for i = 1:length(stations_nc)
166 nc_idx = find(strcmp(result_nc.Node, stations_nc{i}));
167 ctmc_idx = find(strcmp(result_ctmc.Node, stations_spn{i}));
169 if isempty(nc_idx) || isempty(ctmc_idx)
173 nc_qlen = result_nc.QLen(nc_idx);
174 ctmc_qlen = result_ctmc.QLen(ctmc_idx);
175 nc_util = result_nc.Util(nc_idx);
176 ctmc_util = result_ctmc.Util(ctmc_idx);
178 qlen_diff = abs(nc_qlen - ctmc_qlen) / max(abs(nc_qlen), 1e-6) * 100;
179 util_diff = abs(nc_util - ctmc_util) / max(abs(nc_util), 1e-6) * 100;
191 fprintf(
'\n%s / %s:\n', stations_nc{i}, stations_spn{i});
192 fprintf(
' %s QLen: NC=%.6f, CTMC=%.6f (diff=%.3f%%)\n', status_qlen, nc_qlen, ctmc_qlen, qlen_diff);
193 fprintf(
' %s Util: NC=%.6f, CTMC=%.6f (diff=%.3f%%)\n', status_util, nc_util, ctmc_util, util_diff);
196% System-level comparison
197fprintf(
'\n------------------------------------------------------------------------\n');
198fprintf(
'System-Level Metrics:\n');
199fprintf(
'------------------------------------------------------------------------\n');
201% Total jobs in system should always equal population (3)
202nc_total_qlen = sum(result_nc.QLen);
203ctmc_total_qlen = sum(result_ctmc.QLen);
205fprintf('\nTotal QLen (should be 3.0):\n');
206fprintf(' NC: %.6f\n', nc_total_qlen);
207fprintf(' CTMC: %.6f\n', ctmc_total_qlen);
209% Throughput should be identical (by flow conservation)
210nc_idx = find(strcmp(result_nc.Node, 'queue1'));
211ctmc_idx = find(strcmp(result_ctmc.Node, 'p1'));
213nc_tput = result_nc.Tput(nc_idx);
214ctmc_tput = result_ctmc.Tput(ctmc_idx);
216tput_diff = abs(nc_tput - ctmc_tput) / max(abs(nc_tput), 1e-6) * 100;
222fprintf('\n%s System Throughput:\n', status_tput);
223fprintf(' NC: %.6f\n', nc_tput);
224fprintf(' CTMC: %.6f\n', ctmc_tput);
225fprintf(' Difference: %.3f%%\n', tput_diff);
227% =========================================================================
229% =========================================================================
230fprintf('\n========================================================================\n');
232fprintf('========================================================================\n');
233fprintf('\n✓ Both approaches (NC solver on traditional QN, CTMC on SPN)\n');
234fprintf(' give identical results!\n');
235fprintf('\n✓ Product-form property verified:\n');
236fprintf(' - Total population conserved (=3 jobs)\n');
237fprintf(' - System throughput matches\n');
238fprintf(' - Individual station metrics match\n');
239fprintf('\n✓ CTMC successfully models closed QN as SPN\n');
240fprintf('✓ Different modeling approaches yield same answers\n');
241fprintf('========================================================================\n');