2% Validates
the SSA Next-Reaction-Method (NRM) stochastic-Petri-net path on OPEN
3% nets: a Source feeds a Place whose tokens drain through a Transition to a Sink.
4% Before
the Source-arrival reaction was added
the fed Place stayed empty and
the
5% run threw
"Deadlock: no transition is enabled".
7% Each net asserts (a)
the solver actually ran method
'nrm' (never a silent
8% serial fallback) and (b)
the simulated marking mean and throughput match
the
9% analytic M/M/1 result. A Source Exp(lambda) feeding a single-server Transition
10% Exp(mu)
is an M/M/1 queue at
the Place: mean tokens = rho/(1-rho), throughput =
11% lambda (rho = lambda/mu < 1). The canonical net
is cross-checked against JMT.
18%% --- Net 1: M/M/1 SPN, Source Exp(0.5) -> P1 -> T1 Exp(1.0) -> Sink -------
19lambda = 0.5; mu = 1.0; rho = lambda/mu;
20qExact = rho/(1-rho); % = 1.0
21model = mm1spn(lambda, mu);
22solver = SolverSSA(model,
'method',
'nrm',
'samples', SAMPLES,
'seed', SEED);
23[Qn,~,~,Tn] = solver.getAvg();
24assert(~isempty(strfind(solver.result.Avg.method,
'nrm')),
'net1 did not run NRM');
25% stations: Source(1), P1(2)
26assert(abs(Qn(2)-qExact)/qExact < RTOL, sprintf('net1 P1 tokens %g vs exact %g', Qn(2), qExact));
27assert(abs(Tn(1)-lambda)/lambda < RTOL, sprintf('net1 Source tput %g vs %g', Tn(1), lambda));
28assert(abs(Tn(2)-lambda)/lambda < RTOL, sprintf('net1 P1 tput %g vs %g', Tn(2), lambda));
30% Cross-check mean tokens against JMT's simulation of
the same net.
31jmt = SolverJMT(mm1spn(lambda, mu), 'samples', SAMPLES, 'seed', SEED);
32[Qj,~,~,~] = jmt.getAvg();
33assert(abs(Qn(2)-Qj(2))/max(Qj(2),1e-9) < RTOL, sprintf('net1 P1 tokens NRM %g vs JMT %g', Qn(2), Qj(2)));
35%% --- Net 2: open tandem, two places in series ----------------------------
36lambda = 0.5; mu1 = 1.0; mu2 = 2.0;
37q1 = (lambda/mu1)/(1-lambda/mu1); % = 1.0
38q2 = (lambda/mu2)/(1-lambda/mu2); % = 1/3
39model = tandemspn(lambda, mu1, mu2);
40solver = SolverSSA(model, 'method', 'nrm', 'samples', SAMPLES, 'seed', SEED);
41[Qn,~,~,Tn] = solver.getAvg();
42assert(~isempty(strfind(solver.result.Avg.method, 'nrm')), 'net2 did not run NRM');
43% stations: Source(1), P1(2), P2(3)
44assert(abs(Qn(2)-q1)/q1 < RTOL, sprintf('net2 P1 tokens %g vs exact %g', Qn(2), q1));
45assert(abs(Qn(3)-q2)/q2 < RTOL, sprintf('net2 P2 tokens %g vs exact %g', Qn(3), q2));
46assert(abs(Tn(2)-lambda)/lambda < RTOL, sprintf('net2 P1 tput %g vs %g', Tn(2), lambda));
47assert(abs(Tn(3)-lambda)/lambda < RTOL, sprintf('net2 P2 tput %g vs %g', Tn(3), lambda));
49disp('test_spn_nrm_open passed');
51%% --- model builders ------------------------------------------------------
52function model = mm1spn(lambda, mu)
53model = Network('mm1spn');
54source = Source(model, 'Source'); sink = Sink(model, 'Sink');
55P1 = Place(model, 'P1'); T1 = Transition(model, 'T1');
56jobclass = OpenClass(model, 'Class1', 0);
57source.setArrival(
jobclass, Exp(lambda));
58mode = T1.addMode('Mode1'); T1.setDistribution(mode, Exp(mu));
59T1.setEnablingConditions(mode,
jobclass, P1, 1);
60T1.setFiringOutcome(mode,
jobclass, sink, 1);
61R = model.initRoutingMatrix();
62R{1,1}(source,P1) = 1; R{1,1}(P1,T1) = 1; R{1,1}(T1,sink) = 1;
66function model = tandemspn(lambda, mu1, mu2)
67model = Network(
'tandemspn');
68source = Source(model,
'Source'); sink = Sink(model,
'Sink');
69P1 = Place(model,
'P1'); P2 = Place(model,
'P2');
70T1 = Transition(model,
'T1'); T2 = Transition(model,
'T2');
71jobclass = OpenClass(model,
'Class1', 0);
72source.setArrival(
jobclass, Exp(lambda));
73m1 = T1.addMode(
'Mode1'); T1.setDistribution(m1, Exp(mu1));
74T1.setEnablingConditions(m1,
jobclass, P1, 1);
75T1.setFiringOutcome(m1,
jobclass, P2, 1);
76m2 = T2.addMode(
'Mode1'); T2.setDistribution(m2, Exp(mu2));
77T2.setEnablingConditions(m2,
jobclass, P2, 1);
78T2.setFiringOutcome(m2,
jobclass, sink, 1);
79R = model.initRoutingMatrix();
80R{1,1}(source,P1) = 1; R{1,1}(P1,T1) = 1;
81R{1,1}(T1,P2) = 1; R{1,1}(P2,T2) = 1; R{1,1}(T2,sink) = 1;