1function envModel = MAPQN2RENV(model, options)
2% ENVMODEL = MAPQN2RENV(MODEL, OPTIONS)
3% Transform a queueing network with MMPP service into a random environment model
5% This function transforms a queueing network where servers use MMPP2
6% (2-phase Markov Modulated Poisson Process) service distributions into
7% a random environment model with exponential services. The environment
8% has two stages (one per MMPP phase) with transitions defined by the
12% model - Network with MMPP2 service distributions
13% options - Optional
struct with configuration (reserved for future use)
16% envModel - Env model with exponential services modulated by MMPP phases
18% The transformation works as follows:
19% - Input MMPP2 has D0 (phase transitions) and D1 (diagonal service rates)
20% - Output has 2 environment stages with exponential services
21% - Stage 0 uses service rate λ₀ = D1(1,1)
22% - Stage 1 uses service rate λ₁ = D1(2,2)
23% - Environment transitions: σ₀₁ = D0(1,2), σ₁₀ = D0(2,1)
26% model = Network(
'MMPP_Queue');
27% source = Source(model,
'Source');
28% queue = Queue(model,
'Queue', SchedStrategy.FCFS);
29% sink = Sink(model,
'Sink');
30%
jobclass = OpenClass(model,
'Class1');
31% source.setArrival(
jobclass, Exp(1.0));
32% queue.setService(
jobclass, MMPP2(2.0, 3.0, 0.5, 0.5));
33% model.link(Network.serialRouting(source, queue, sink));
34% envModel = MAPQN2RENV(model);
37 if ~isa(model,
'Network')
38 error('MAPQN2RENV:InvalidInput', 'First argument must be a Network
object');
41 % Phase 1: Input Validation
42 % Verify all service distributions are MMPP2 and D1
is diagonal
43 [mmpp2Found, mmppParams] = validateAndExtractMMPP(model);
46 error('MAPQN2RENV:NoMMPP', 'Network must contain at least one MMPP2 service distribution');
49 % Phase 2: Extract MMPP Parameters
53 % Extract service rates from D1 diagonal
57 % Extract transition rates from D0 off-diagonal
61 % Validate rates are non-negative
62 if lambda0 < 0 || lambda1 < 0 || sigma01 < 0 || sigma10 < 0
63 error('MAPQN2RENV:InvalidRates', 'All extracted rates must be non-negative');
66 % Phase 3: Create Environment Model
67 envModel = Env('MAPQN_Env');
69 % Phase 4: Build Stage Networks
70 % Create stage network for Phase 0
71 stageNet0 = buildStageNetwork(model, 'Phase0', lambda0);
72 envModel.addStage('Phase0', 'item', stageNet0);
74 % Create stage network for Phase 1
75 stageNet1 = buildStageNetwork(model, 'Phase1', lambda1);
76 envModel.addStage('Phase1', 'item', stageNet1);
78 % Phase 5: Add Environment Transitions
80 envModel.addTransition('Phase0', 'Phase1', Exp(sigma01));
84 envModel.addTransition('Phase1', 'Phase0', Exp(sigma10));
87 % Initialize environment
91%% Helper: Validate MMPP2 and Extract Parameters
92function [found, params] = validateAndExtractMMPP(model)
99 % Iterate through all
nodes to find MMPP2 distributions
100 for i = 1:length(
nodes)
103 % Check
if node
is a Queue or Delay
104 if ~(isa(node,
'Queue') || isa(node,
'Delay'))
108 % Check service distributions
for all
classes
109 if isa(node,
'Queue')
110 for r = 1:length(node.server.serviceProcess)
111 if ~isempty(node.server.serviceProcess{r})
112 dist = node.server.serviceProcess{r}{end};
114 if isa(dist,
'MMPP2')
115 if isempty(firstMMPP2)
119 % Verify consistency with first MMPP2
120 verifyMMPPConsistency(firstMMPP2, dist);
122 elseif isa(dist, 'MAP') && ~isa(dist, 'MMPP2')
124 D0_check = dist.D(0);
125 D1_check = dist.D(1);
127 % Check if D1
is diagonal
128 for ii = 1:size(D1_check, 1)
129 for jj = 1:size(D1_check, 2)
130 if ii ~= jj && abs(D1_check(ii, jj)) > GlobalConstants.Zero
131 line_warning(mfilename, 'Generic MAP detected. Only MMPP (diagonal D1)
is supported. Aborting transformation.');
132 error('MAPQN2RENV:GenericMAP', 'Generic MAP with non-diagonal D1
is not supported. Use MMPP2 instead.');
142 if found && ~isempty(firstMMPP2)
143 D0 = firstMMPP2.D(0);
144 D1 = firstMMPP2.D(1);
151%% Helper: Verify MMPP Consistency
152function verifyMMPPConsistency(mmpp1, mmpp2)
153 % Future: Could add checks for consistent MMPP parameters across network
154 % For now, just allow multiple MMPP2 with same parameters
157%% Helper: Build Stage Network
158function stageNet = buildStageNetwork(originalModel, stageName, expRate)
159 % Clone the network structure and replace MMPP2 with Exp
160 stageNet = Network([originalModel.name '_' stageName]);
162 % Build mapping from original
nodes to cloned
nodes
163 nodeMap = containers.Map('KeyType', '
char', 'ValueType', 'any');
164 origClasses = originalModel.getClasses();
166 % PASS 1: Create all
nodes first (Source, Sink, Delay, Queue)
168 for i = 1:length(originalModel.
nodes)
169 origNode = originalModel.
nodes{i};
170 if isa(origNode,
'Source')
171 newNode = Source(stageNet, origNode.name);
172 nodeMap(origNode.name) = newNode;
173 elseif isa(origNode, 'Sink')
174 newNode = Sink(stageNet, origNode.name);
175 nodeMap(origNode.name) = newNode;
176 elseif isa(origNode, 'Delay')
177 newNode = Delay(stageNet, origNode.name);
178 nodeMap(origNode.name) = newNode;
179 elseif isa(origNode, 'Queue')
180 newNode = Queue(stageNet, origNode.name, origNode.schedStrategy);
181 % Copy number of servers and capacity
182 if hasProperty(origNode, 'numberOfServers')
183 newNode.setNumberOfServers(origNode.numberOfServers);
185 if hasProperty(origNode, 'cap')
186 newNode.setCapacity(origNode.cap);
188 nodeMap(origNode.name) = newNode;
192 % PASS 2: Create all job
classes (all
nodes must exist first)
193 for r = 1:length(origClasses)
194 origClass = origClasses{r};
195 if isa(origClass,
'OpenClass')
196 OpenClass(stageNet, origClass.name);
197 elseif isa(origClass, 'ClosedClass')
198 % Find reference station in the new network
199 origRefStat = origClass.refstat;
200 if isKey(nodeMap, origRefStat.name)
201 newRefStat = nodeMap(origRefStat.name);
203 % Fallback to first node if reference station not yet created
204 newRefStat = stageNet.
nodes{1};
206 ClosedClass(stageNet, origClass.name, origClass.population, newRefStat);
210 % PASS 4: Set arrivals from Source
nodes
211 for i = 1:length(originalModel.nodes)
212 origNode = originalModel.
nodes{i};
214 if isa(origNode,
'Source') && isKey(nodeMap, origNode.name)
215 newNode = nodeMap(origNode.name);
217 % Copy arrival distributions
218 origInput = origNode.input;
219 if ~isempty(origInput) && ~isempty(origInput.sourceClasses)
220 for r = 1:length(origInput.sourceClasses)
221 if ~isempty(origInput.sourceClasses{r})
222 origDist = origInput.sourceClasses{r}{end};
223 classes = stageNet.getClasses();
226 newNode.setArrival(jobClass, origDist);
234 % PASS 5: Set services on Queue/Delay
nodes, replacing MMPP2 with Exp
235 for i = 1:length(originalModel.nodes)
236 origNode = originalModel.
nodes{i};
238 if (isa(origNode,
'Queue') || isa(origNode,
'Delay')) && isKey(nodeMap, origNode.name)
239 newNode = nodeMap(origNode.name);
240 classes = stageNet.getClasses();
242 % Copy service distributions, replacing MMPP2 with Exp
243 if isa(origNode, 'Queue') && hasProperty(origNode, 'server')
244 for r = 1:length(origNode.server.serviceProcess)
245 if ~isempty(origNode.server.serviceProcess{r})
246 origDist = origNode.server.serviceProcess{r}{end};
248 if isa(origDist,
'MMPP2')
249 % Replace with exponential
250 newDist = Exp(expRate);
252 % Keep other distributions as-
is
256 % Set service
for this class
259 newNode.setService(jobClass, newDist);
263 elseif isa(origNode,
'Delay') && hasProperty(origNode,
'server')
264 % Handle Delay node services
265 for r = 1:length(origNode.server.serviceProcess)
266 if ~isempty(origNode.server.serviceProcess{r})
267 origDist = origNode.server.serviceProcess{r}{end};
269 if isa(origDist,
'MMPP2')
270 % Replace with exponential
271 newDist = Exp(expRate);
273 % Keep other distributions as-
is
277 % Set service
for this class
280 newNode.setService(jobClass, newDist);
288 % PASS 6: Setup routing from original model
289 % Determine
if this is an open or closed network
290 isClosedNetwork = any(cellfun(@(c) isa(c,
'ClosedClass'), origClasses));
292 % Collect route
nodes in order
294 for i = 1:length(originalModel.nodes)
295 origNode = originalModel.
nodes{i};
296 if isKey(nodeMap, origNode.name)
297 routeNodes{end+1} = nodeMap(origNode.name);
301 % Setup routing based on network type
303 % Closed network: use cyclic routing
304 if length(routeNodes) >= 2
306 stageNet.link(Network.serialRouting(routeNodes{:}));
308 % If serial routing fails, create cyclic routing manually
309 for i = 1:length(routeNodes)
310 fromNode = routeNodes{i};
311 toNode = routeNodes{mod(i, length(routeNodes)) + 1};
313 P = stageNet.initRoutingMatrix();
314 for r = 1:length(stageNet.getClasses())
315 P{r, r}(stageNet.getNodeIndex(fromNode), stageNet.getNodeIndex(toNode)) = 1.0;
325 % Open network: use serial routing
326 if length(routeNodes) > 2
328 stageNet.link(Network.serialRouting(routeNodes{:}));
330 % If serial routing fails,
try manual node-to-node routing
331 for i = 1:length(routeNodes)-1
332 fromNode = routeNodes{i};
333 toNode = routeNodes{i+1};
335 stageNet.setRouting(fromNode, toNode, 1.0);
341 elseif length(routeNodes) == 2
343 stageNet.link(Network.serialRouting(routeNodes{1}, routeNodes{2}));
345 % Skip routing
if fails
351%% Helper: Check
if object has
property
352function hasIt = hasProperty(obj, propName)
354 dummy = obj.(propName); %#ok<NASGU>