LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
linemodel_load.m
1function model = linemodel_load(filename)
2% LINEMODEL_LOAD Load a LINE model (Network or LayeredNetwork) from JSON.
3%
4% MODEL = LINEMODEL_LOAD(FILENAME) loads a model from the specified JSON
5% file (conforming to line-model.schema.json) and returns a Network or
6% LayeredNetwork object.
7%
8% Parameters:
9% filename - path to a .json file
10%
11% Returns:
12% model - Network or LayeredNetwork object
13%
14% Example:
15% model = linemodel_load('mm1.json');
16% solver = SolverMVA(model);
17% AvgTable = solver.getAvgTable();
18%
19% Copyright (c) 2012-2026, Imperial College London
20% All rights reserved.
21
22jsonText = fileread(filename);
23doc = jsondecode(jsonText);
24
25if ~isfield(doc, 'model')
26 error('linemodel_load:noModel', 'JSON file does not contain a "model" field.');
27end
28
29data = doc.model;
30mtype = data.type;
31
32switch mtype
33 case 'Network'
34 model = json2network(data, jsonText);
35 case 'LayeredNetwork'
36 model = json2layered(data);
37 otherwise
38 error('linemodel_load:unknownType', 'Unsupported model type: %s', mtype);
39end
40end
41
42
43% =========================================================================
44% Network deserialization
45% =========================================================================
46
47function model = json2network(data, rawJson)
48% Reconstruct a Network from decoded JSON struct.
49% rawJson is the original text, used for parsing routing keys with commas.
50
51modelName = 'model';
52if isfield(data, 'name')
53 modelName = data.name;
54end
55model = Network(modelName);
56
57% --- Create nodes (before classes, since ClosedClass needs refstat) ---
58nodeList = {};
59if isfield(data, 'nodes')
60 nds = data.nodes;
61 if isstruct(nds)
62 nds = num2cell(nds);
63 end
64 for i = 1:length(nds)
65 nd = nds{i};
66 if isstruct(nd)
67 nd_name = nd.name;
68 nd_type = nd.type;
69 else
70 nd_name = nd('name');
71 nd_type = nd('type');
72 end
73 node = create_node(model, nd, nd_name, nd_type);
74 nodeList{end+1} = node; %#ok<AGROW>
75 end
76end
77node_map = containers.Map();
78for i = 1:length(nodeList)
79 node_map(nodeList{i}.name) = nodeList{i};
80end
81
82% --- Deferred node linking (Fork/Join, Fork tasksPerLink) ---
83if isfield(data, 'nodes')
84 nds2 = data.nodes;
85 if isstruct(nds2)
86 nds2 = num2cell(nds2);
87 end
88 for i = 1:length(nds2)
89 nd2 = nds2{i};
90 nd_name2 = nd2.name;
91 node2 = node_map(nd_name2);
92 % Join: link to paired Fork
93 if isfield(nd2, 'forkNode') && isa(node2, 'Join')
94 if node_map.isKey(nd2.forkNode)
95 node2.joinOf = node_map(nd2.forkNode);
96 end
97 end
98 % Fork: set tasksPerLink
99 if isfield(nd2, 'tasksPerLink') && isa(node2, 'Fork')
100 node2.setTasksPerLink(nd2.tasksPerLink);
101 end
102 end
103end
104
105% --- Create classes ---
106classesList = {};
107if isfield(data, 'classes')
108 cls = data.classes;
109 if isstruct(cls)
110 cls = num2cell(cls);
111 end
112 for i = 1:length(cls)
113 cd = cls{i};
114 cname = cd.name;
115 ctype = cd.type;
116 switch ctype
117 case 'Open'
118 prio = 0;
119 if isfield(cd, 'priority'), prio = cd.priority; end
120 jc = OpenClass(model, cname, prio);
121 case 'Closed'
122 pop = cd.population;
123 refNode = [];
124 if isfield(cd, 'refNode') && node_map.isKey(cd.refNode)
125 refNode = node_map(cd.refNode);
126 end
127 prio = 0;
128 if isfield(cd, 'priority'), prio = cd.priority; end
129 if isempty(refNode)
130 error('linemodel_load:noRefNode', ...
131 'ClosedClass "%s" has no valid refNode.', cname);
132 end
133 jc = ClosedClass(model, cname, pop, refNode, prio);
134 otherwise
135 jc = OpenClass(model, cname);
136 end
137 classesList{end+1} = jc; %#ok<AGROW>
138 end
139end
140class_map = containers.Map();
141for i = 1:length(classesList)
142 class_map(classesList{i}.name) = classesList{i};
143end
144
145% --- Set service/arrival distributions ---
146if isfield(data, 'nodes')
147 nds = data.nodes;
148 if isstruct(nds)
149 nds = num2cell(nds);
150 end
151 for i = 1:length(nds)
152 nd = nds{i};
153 nd_name = nd.name;
154 node = node_map(nd_name);
155
156 if isfield(nd, 'service') && ~isempty(nd.service)
157 svc = nd.service;
158 svcFields = fieldnames(svc);
159 for f = 1:length(svcFields)
160 cname = svcFields{f};
161 distJson = svc.(cname);
162 if ~class_map.isKey(cname)
163 continue;
164 end
165 jc = class_map(cname);
166 dist = json2dist(distJson);
167 if ~isempty(dist)
168 if isa(node, 'Source')
169 node.setArrival(jc, dist);
170 elseif isa(node, 'Queue') || isa(node, 'Delay')
171 node.setService(jc, dist);
172 end
173 end
174 end
175 end
176
177 % ClassSwitch matrix (dict format: classSwitchMatrix)
178 if isfield(nd, 'classSwitchMatrix') && isa(node, 'ClassSwitch')
179 csm_data = nd.classSwitchMatrix;
180 classes = model.getClasses();
181 K = length(classes);
182 mat = zeros(K);
183 class_idx = containers.Map();
184 for ci = 1:K
185 class_idx(classes{ci}.name) = ci;
186 end
187 fromFields = fieldnames(csm_data);
188 for fi = 1:length(fromFields)
189 fromName = fromFields{fi};
190 if ~class_idx.isKey(fromName), continue; end
191 ri = class_idx(fromName);
192 toStruct = csm_data.(fromName);
193 toFields = fieldnames(toStruct);
194 for ti = 1:length(toFields)
195 toName = toFields{ti};
196 if ~class_idx.isKey(toName), continue; end
197 ci = class_idx(toName);
198 mat(ri, ci) = toStruct.(toName);
199 end
200 end
201 node.server = node.server.updateClassSwitch(mat);
202 % Legacy 2D array format: csMatrix (from older JAR saves)
203 elseif isfield(nd, 'csMatrix') && isa(node, 'ClassSwitch')
204 mat = nd.csMatrix;
205 if iscell(mat)
206 mat = cell2mat(mat);
207 end
208 node.server = node.server.updateClassSwitch(mat);
209 end
210
211 % DPS scheduling parameters
212 if isfield(nd, 'schedParams') && isa(node, 'Queue')
213 sp = nd.schedParams;
214 spFields = fieldnames(sp);
215 for sfi = 1:length(spFields)
216 cname = spFields{sfi};
217 if class_map.isKey(cname)
218 jc = class_map(cname);
219 cidx = 0;
220 for ki = 1:length(classesList)
221 if strcmp(classesList{ki}.name, cname)
222 cidx = ki;
223 break;
224 end
225 end
226 if cidx > 0
227 node.schedStrategyPar(cidx) = sp.(cname);
228 end
229 end
230 end
231 end
232
233 % Cache hit/miss class mappings and popularity distributions
234 if isa(node, 'Cache') && isfield(nd, 'cache')
235 cc = nd.cache;
236 % Hit class mapping
237 if isfield(cc, 'hitClass')
238 hcData = cc.hitClass;
239 hcFields = fieldnames(hcData);
240 for hci = 1:length(hcFields)
241 inName = hcFields{hci};
242 outName = hcData.(inName);
243 if class_map.isKey(inName) && class_map.isKey(outName)
244 node.setHitClass(class_map(inName), class_map(outName));
245 end
246 end
247 end
248 % Miss class mapping
249 if isfield(cc, 'missClass')
250 mcData = cc.missClass;
251 mcFields = fieldnames(mcData);
252 for mci = 1:length(mcFields)
253 inName = mcFields{mci};
254 outName = mcData.(inName);
255 if class_map.isKey(inName) && class_map.isKey(outName)
256 node.setMissClass(class_map(inName), class_map(outName));
257 end
258 end
259 end
260 % Popularity distributions (setRead)
261 if isfield(cc, 'popularity')
262 popData = cc.popularity;
263 popFields = fieldnames(popData);
264 for pfi = 1:length(popFields)
265 cname = popFields{pfi};
266 if class_map.isKey(cname)
267 popDist = json2dist(popData.(cname));
268 if ~isempty(popDist)
269 node.setRead(class_map(cname), popDist);
270 end
271 end
272 end
273 end
274 end
275 end
276end
277
278% --- Configure Transition modes ---
279if isfield(data, 'nodes')
280 nds3 = data.nodes;
281 if isstruct(nds3)
282 nds3 = num2cell(nds3);
283 end
284 for i = 1:length(nds3)
285 nd3 = nds3{i};
286 if ~isfield(nd3, 'modes'), continue; end
287 if ~strcmp(nd3.type, 'Transition'), continue; end
288 tnode = node_map(nd3.name);
289 modesData = nd3.modes;
290 if isstruct(modesData)
291 modesData = num2cell(modesData);
292 end
293 for mi = 1:length(modesData)
294 md = modesData{mi};
295 modeName = 'Mode';
296 if isfield(md, 'name'), modeName = md.name; end
297 mode = tnode.addMode(modeName);
298 % Distribution
299 if isfield(md, 'distribution') && ~isempty(md.distribution)
300 dist = json2dist(md.distribution);
301 if ~isempty(dist)
302 tnode.setDistribution(mode, dist);
303 end
304 end
305 % Timing strategy
306 if isfield(md, 'timingStrategy')
307 if strcmp(md.timingStrategy, 'IMMEDIATE')
308 tnode.setTimingStrategy(mode, TimingStrategy.IMMEDIATE);
309 else
310 tnode.setTimingStrategy(mode, TimingStrategy.TIMED);
311 end
312 end
313 % Number of servers
314 if isfield(md, 'numServers') && md.numServers > 1
315 tnode.setNumberOfServers(mode, md.numServers);
316 end
317 % Firing priority
318 if isfield(md, 'firingPriority')
319 tnode.setFiringPriorities(mode, md.firingPriority);
320 end
321 % Firing weight
322 if isfield(md, 'firingWeight')
323 tnode.setFiringWeights(mode, md.firingWeight);
324 end
325 % Enabling conditions
326 if isfield(md, 'enablingConditions')
327 ecList = md.enablingConditions;
328 if isstruct(ecList), ecList = num2cell(ecList); end
329 for ei = 1:length(ecList)
330 ec = ecList{ei};
331 if node_map.isKey(ec.node) && class_map.isKey(ec.class)
332 tnode.setEnablingConditions(mode, class_map(ec.class), node_map(ec.node), ec.count);
333 end
334 end
335 end
336 % Inhibiting conditions
337 if isfield(md, 'inhibitingConditions')
338 icList = md.inhibitingConditions;
339 if isstruct(icList), icList = num2cell(icList); end
340 for ii = 1:length(icList)
341 ic = icList{ii};
342 if node_map.isKey(ic.node) && class_map.isKey(ic.class)
343 tnode.setInhibitingConditions(mode, class_map(ic.class), node_map(ic.node), ic.count);
344 end
345 end
346 end
347 % Firing outcomes
348 if isfield(md, 'firingOutcomes')
349 foList = md.firingOutcomes;
350 if isstruct(foList), foList = num2cell(foList); end
351 for fi = 1:length(foList)
352 fo = foList{fi};
353 if node_map.isKey(fo.node) && class_map.isKey(fo.class)
354 tnode.setFiringOutcome(mode, class_map(fo.class), node_map(fo.node), fo.count);
355 end
356 end
357 end
358 end
359 end
360end
361
362% --- Build routing ---
363if isfield(data, 'routing') && isfield(data.routing, 'type') && strcmp(data.routing.type, 'matrix')
364 P = model.initRoutingMatrix();
365 K = length(classesList);
366 M = length(nodeList);
367
368 % Build node/class index maps
369 nodeIdx = containers.Map();
370 for i = 1:M
371 nodeIdx(nodeList{i}.name) = i;
372 end
373 classIdx = containers.Map();
374 for i = 1:K
375 classIdx(classesList{i}.name) = i;
376 end
377
378 % Parse routing keys from raw JSON to preserve commas
379 routingEntries = parse_routing_keys(rawJson, class_map, node_map);
380
381 for e = 1:length(routingEntries)
382 re = routingEntries{e};
383 r = classIdx(re.className1);
384 s = classIdx(re.className2);
385 ii = nodeIdx(re.fromNode);
386 jj = nodeIdx(re.toNode);
387 P{r,s}(ii, jj) = re.prob;
388 end
389
390 model.link(P);
391end
392end
393
394
395function node = create_node(model, nd, name, ntype)
396% Create a node from JSON data.
397switch ntype
398 case 'Source'
399 node = Source(model, name);
400 case 'Sink'
401 node = Sink(model, name);
402 case 'Delay'
403 node = Delay(model, name);
404 case 'Queue'
405 schedStr = 'FCFS';
406 if isfield(nd, 'scheduling')
407 schedStr = nd.scheduling;
408 end
409 schedId = str_to_sched_id(schedStr);
410 node = Queue(model, name, schedId);
411 if isfield(nd, 'servers') && nd.servers > 1
412 node.setNumberOfServers(nd.servers);
413 end
414 if isfield(nd, 'buffer') && isfinite(nd.buffer)
415 node.cap = nd.buffer;
416 end
417 case 'Fork'
418 node = Fork(model, name);
419 case 'Join'
420 node = Join(model, name);
421 case 'Router'
422 node = Router(model, name);
423 case 'ClassSwitch'
424 node = ClassSwitch(model, name);
425 case 'Cache'
426 cc = struct();
427 if isfield(nd, 'cache')
428 cc = nd.cache;
429 end
430 nitems = 10;
431 if isfield(cc, 'items'), nitems = cc.items; end
432 cap = 1;
433 if isfield(cc, 'capacity'), cap = cc.capacity; end
434 replStr = 'LRU';
435 if isfield(cc, 'replacement'), replStr = cc.replacement; end
436 replId = str_to_repl_id(replStr);
437 node = Cache(model, name, nitems, cap, replId);
438 case 'Place'
439 node = Place(model, name);
440 case 'Transition'
441 node = Transition(model, name);
442 otherwise
443 node = Queue(model, name, SchedStrategy.FCFS);
444end
445end
446
447
448% =========================================================================
449% LayeredNetwork deserialization
450% =========================================================================
451
452function model = json2layered(data)
453% Reconstruct a LayeredNetwork from decoded JSON struct.
454
455modelName = 'model';
456if isfield(data, 'name')
457 modelName = data.name;
458end
459model = LayeredNetwork(modelName);
460
461% --- Processors ---
462proc_map = containers.Map();
463if isfield(data, 'processors')
464 procs = data.processors;
465 if isstruct(procs), procs = num2cell(procs); end
466 for i = 1:length(procs)
467 pd = procs{i};
468 pname = pd.name;
469 mult = 1;
470 if isfield(pd, 'multiplicity'), mult = pd.multiplicity; end
471 schedStr = 'INF';
472 if isfield(pd, 'scheduling'), schedStr = pd.scheduling; end
473 schedId = str_to_sched_id(schedStr);
474 quantum = 0.001;
475 if isfield(pd, 'quantum'), quantum = pd.quantum; end
476 sf = 1.0;
477 if isfield(pd, 'speedFactor'), sf = pd.speedFactor; end
478 proc = Host(model, pname, mult, schedId, quantum, sf);
479 if isfield(pd, 'replication') && pd.replication > 1
480 proc.setReplication(pd.replication);
481 end
482 proc_map(pname) = proc;
483 end
484end
485
486% --- Tasks ---
487task_map = containers.Map();
488if isfield(data, 'tasks')
489 tsks = data.tasks;
490 if isstruct(tsks), tsks = num2cell(tsks); end
491 for i = 1:length(tsks)
492 td = tsks{i};
493 tname = td.name;
494 mult = 1;
495 if isfield(td, 'multiplicity'), mult = td.multiplicity; end
496 schedStr = 'INF';
497 if isfield(td, 'scheduling'), schedStr = td.scheduling; end
498 schedId = str_to_sched_id(schedStr);
499 task = Task(model, tname, mult, schedId);
500 % Assign to processor
501 if isfield(td, 'processor') && proc_map.isKey(td.processor)
502 task.on(proc_map(td.processor));
503 end
504 % Think time
505 if isfield(td, 'thinkTime')
506 dist = json2dist(td.thinkTime);
507 if ~isempty(dist)
508 task.setThinkTime(dist);
509 end
510 end
511 % Fan in
512 if isfield(td, 'fanIn') && isstruct(td.fanIn)
513 fnames = fieldnames(td.fanIn);
514 for fi = 1:length(fnames)
515 task.setFanIn(fnames{fi}, td.fanIn.(fnames{fi}));
516 end
517 end
518 % Fan out
519 if isfield(td, 'fanOut') && isstruct(td.fanOut)
520 fnames = fieldnames(td.fanOut);
521 for fi = 1:length(fnames)
522 task.setFanOut(fnames{fi}, td.fanOut.(fnames{fi}));
523 end
524 end
525 % Replication
526 if isfield(td, 'replication') && td.replication > 1
527 task.setReplication(td.replication);
528 end
529 task_map(tname) = task;
530 end
531end
532
533% --- Entries ---
534entry_map = containers.Map();
535if isfield(data, 'entries')
536 ents = data.entries;
537 if isstruct(ents), ents = num2cell(ents); end
538 for i = 1:length(ents)
539 ed = ents{i};
540 ename = ed.name;
541 entry = Entry(model, ename);
542 if isfield(ed, 'task') && task_map.isKey(ed.task)
543 entry.on(task_map(ed.task));
544 end
545 entry_map(ename) = entry;
546 end
547end
548
549% --- Activities ---
550act_map = containers.Map();
551if isfield(data, 'activities')
552 acts = data.activities;
553 if isstruct(acts), acts = num2cell(acts); end
554 for i = 1:length(acts)
555 ad = acts{i};
556 aname = ad.name;
557
558 % Host demand
559 hd = GlobalConstants.FineTol;
560 if isfield(ad, 'hostDemand')
561 hdDist = json2dist(ad.hostDemand);
562 if ~isempty(hdDist)
563 hd = hdDist;
564 end
565 end
566
567 % Bound to entry
568 bte = '';
569 if isfield(ad, 'boundTo')
570 bte = ad.boundTo;
571 end
572
573 act = Activity(model, aname, hd, bte);
574
575 % Assign to task
576 if isfield(ad, 'task') && task_map.isKey(ad.task)
577 act.on(task_map(ad.task));
578 end
579
580 % Replies to entry
581 if isfield(ad, 'repliesTo') && entry_map.isKey(ad.repliesTo)
582 act.repliesTo(entry_map(ad.repliesTo));
583 end
584
585 % Synch calls
586 if isfield(ad, 'synchCalls')
587 scs = ad.synchCalls;
588 if isstruct(scs), scs = num2cell(scs); end
589 for j = 1:length(scs)
590 sc = scs{j};
591 ename = sc.entry;
592 meanCalls = 1.0;
593 if isfield(sc, 'mean'), meanCalls = sc.mean; end
594 if entry_map.isKey(ename)
595 act.synchCall(entry_map(ename), meanCalls);
596 end
597 end
598 end
599
600 % Asynch calls
601 if isfield(ad, 'asynchCalls')
602 acs = ad.asynchCalls;
603 if isstruct(acs), acs = num2cell(acs); end
604 for j = 1:length(acs)
605 ac = acs{j};
606 ename = ac.entry;
607 meanCalls = 1.0;
608 if isfield(ac, 'mean'), meanCalls = ac.mean; end
609 if entry_map.isKey(ename)
610 act.asynchCall(entry_map(ename), meanCalls);
611 end
612 end
613 end
614
615 act_map(aname) = act;
616 end
617end
618
619% --- Precedences ---
620if isfield(data, 'precedences')
621 precs = data.precedences;
622 if isstruct(precs), precs = num2cell(precs); end
623 for i = 1:length(precs)
624 pd = precs{i};
625 if ~isfield(pd, 'task') || ~task_map.isKey(pd.task)
626 continue;
627 end
628 task = task_map(pd.task);
629 ptype = pd.type;
630 actNames = pd.activities;
631 if ischar(actNames), actNames = {actNames}; end
632
633 % Resolve activity names to objects
634 actObjs = {};
635 for ai = 1:length(actNames)
636 an = actNames{ai};
637 if act_map.isKey(an)
638 actObjs{end+1} = act_map(an); %#ok<AGROW>
639 end
640 end
641 if length(actObjs) < 2
642 continue;
643 end
644
645 switch ptype
646 case 'Serial'
647 ap = ActivityPrecedence.Serial(actObjs{:});
648 task.addPrecedence(ap);
649 case 'AndFork'
650 ap = ActivityPrecedence.AndFork(actObjs{1}, actObjs(2:end));
651 task.addPrecedence(ap);
652 case 'AndJoin'
653 ap = ActivityPrecedence.AndJoin(actObjs(1:end-1), actObjs{end});
654 task.addPrecedence(ap);
655 case 'OrFork'
656 probs = [];
657 if isfield(pd, 'probabilities')
658 probs = pd.probabilities;
659 if isstruct(probs), probs = cell2mat(struct2cell(probs)); end
660 end
661 if isempty(probs)
662 n = length(actObjs) - 1;
663 probs = ones(1, n) / n;
664 end
665 ap = ActivityPrecedence.OrFork(actObjs{1}, actObjs(2:end), probs);
666 task.addPrecedence(ap);
667 case 'OrJoin'
668 ap = ActivityPrecedence.OrJoin(actObjs(1:end-1), actObjs{end});
669 task.addPrecedence(ap);
670 case 'Loop'
671 count = 1.0;
672 if isfield(pd, 'loopCount'), count = pd.loopCount; end
673 % Loop(preAct, postActs, counts)
674 % preAct = first, loop body = middle, end = last
675 if length(actObjs) >= 3
676 ap = ActivityPrecedence.Loop(actObjs{1}, actObjs(2:end-1), actObjs{end}, count);
677 else
678 ap = ActivityPrecedence.Loop(actObjs{1}, actObjs(2:end), count);
679 end
680 task.addPrecedence(ap);
681 end
682 end
683end
684end
685
686
687% =========================================================================
688% Distribution deserialization
689% =========================================================================
690
691function dist = json2dist(d)
692% Convert a JSON distribution struct to a MATLAB Distribution object.
693if isempty(d)
694 dist = [];
695 return;
696end
697
698dtype = d.type;
699
700switch dtype
701 case 'Disabled'
702 dist = Disabled.getInstance();
703 return;
704 case 'Immediate'
705 dist = Immediate.getInstance();
706 return;
707end
708
709% Direct params
710if isfield(d, 'params') && ~isempty(d.params)
711 p = d.params;
712 switch dtype
713 case 'Exp'
714 lam = p.lambda;
715 dist = Exp(lam);
716 return;
717 case 'Det'
718 dist = Det(p.value);
719 return;
720 case 'Erlang'
721 dist = Erlang(p.lambda, p.k);
722 return;
723 case 'HyperExp'
724 pv = p.p;
725 lv = p.lambda;
726 if isscalar(pv)
727 dist = HyperExp(pv, lv(1), lv(2));
728 else
729 dist = HyperExp(pv(1), lv(1), lv(2));
730 end
731 return;
732 case 'Gamma'
733 dist = Gamma(p.alpha, p.beta);
734 return;
735 case 'Lognormal'
736 dist = Lognormal(p.mu, p.sigma);
737 return;
738 case 'Uniform'
739 dist = Uniform(p.a, p.b);
740 return;
741 case 'Zipf'
742 dist = Zipf(p.s, p.n);
743 return;
744 case 'Pareto'
745 dist = Pareto(p.alpha, p.scale);
746 return;
747 case 'DiscreteSampler'
748 pv = p.p(:)';
749 xv = p.x(:)';
750 dist = DiscreteSampler(pv, xv);
751 return;
752 end
753end
754
755% PH representation
756if isfield(d, 'ph') && ~isempty(d.ph)
757 ph = d.ph;
758 alpha = ph.alpha;
759 T = ph.T;
760 if ~isvector(alpha), alpha = alpha(:)'; end
761 dist = PH(alpha, T);
762 return;
763end
764
765% MAP representation
766if isfield(d, 'map') && ~isempty(d.map)
767 mapSpec = d.map;
768 D0 = mapSpec.D0;
769 D1 = mapSpec.D1;
770 dist = MAP(D0, D1);
771 return;
772end
773
774% Fit specification
775if isfield(d, 'fit') && ~isempty(d.fit)
776 fit = d.fit;
777 method = fit.method;
778 switch method
779 case 'fitMean'
780 m = fit.mean;
781 switch dtype
782 case 'Exp'
783 dist = Exp(1.0 / m);
784 case 'Det'
785 dist = Det(m);
786 otherwise
787 dist = Exp(1.0 / m);
788 end
789 return;
790 case 'fitMeanAndSCV'
791 m = fit.mean;
792 scv = fit.scv;
793 switch dtype
794 case 'Erlang'
795 dist = Erlang.fitMeanAndSCV(m, scv);
796 case 'HyperExp'
797 dist = HyperExp.fitMeanAndSCV(m, scv);
798 otherwise
799 dist = Exp(1.0 / m);
800 end
801 return;
802 case 'fitMeanAndOrder'
803 m = fit.mean;
804 order = fit.order;
805 switch dtype
806 case 'Erlang'
807 dist = Erlang.fitMeanAndOrder(m, order);
808 otherwise
809 dist = Exp(1.0 / m);
810 end
811 return;
812 end
813end
814
815% Fallback
816dist = Exp(1.0);
817end
818
819
820% =========================================================================
821% Routing parser (handles comma keys in JSON)
822% =========================================================================
823
824function entries = parse_routing_keys(rawJson, class_map, node_map)
825% Parse routing matrix from raw JSON text to handle keys with commas.
826% Returns a cell array of structs with fields:
827% className1, className2, fromNode, toNode, prob
828entries = {};
829
830% Build reverse mapping: jsondecode-sanitized name -> original node name
831% jsondecode uses matlab.lang.makeValidName which replaces spaces etc.
832nodeNames = node_map.keys();
833sanitized_map = containers.Map();
834for ni = 1:length(nodeNames)
835 origName = nodeNames{ni};
836 sanitized = matlab.lang.makeValidName(origName);
837 sanitized_map(sanitized) = origName;
838end
839
840classNames = class_map.keys();
841
842% For each pair of class names, try to find the corresponding key in the JSON
843for ri = 1:length(classNames)
844 for si = 1:length(classNames)
845 cn1 = classNames{ri};
846 cn2 = classNames{si};
847 keyStr = ['"', cn1, ',', cn2, '"'];
848
849 % Find this key in the raw JSON
850 pos = strfind(rawJson, keyStr);
851 if isempty(pos)
852 continue;
853 end
854
855 % For each occurrence, extract the nested from -> to -> prob structure
856 for pidx = 1:length(pos)
857 startPos = pos(pidx) + length(keyStr);
858 % Skip whitespace and colon
859 idx = startPos;
860 while idx <= length(rawJson) && (rawJson(idx) == ' ' || rawJson(idx) == ':' || rawJson(idx) == newline || rawJson(idx) == char(13) || rawJson(idx) == char(9))
861 idx = idx + 1;
862 end
863 if idx > length(rawJson) || rawJson(idx) ~= '{'
864 continue;
865 end
866 % Extract the JSON object using brace counting
867 objStr = extract_json_object(rawJson, idx);
868 if isempty(objStr)
869 continue;
870 end
871 % Parse the from -> to -> prob structure
872 try
873 fromTo = jsondecode(objStr);
874 fromNames = fieldnames(fromTo);
875 for fi = 1:length(fromNames)
876 fromField = fromNames{fi};
877 toStruct = fromTo.(fromField);
878 toNames = fieldnames(toStruct);
879 % Resolve sanitized field names back to original node names
880 if sanitized_map.isKey(fromField)
881 fromName = sanitized_map(fromField);
882 else
883 fromName = fromField;
884 end
885 for ti = 1:length(toNames)
886 toField = toNames{ti};
887 prob = toStruct.(toField);
888 if sanitized_map.isKey(toField)
889 toName = sanitized_map(toField);
890 else
891 toName = toField;
892 end
893 % Verify names exist in the model
894 if node_map.isKey(fromName) && node_map.isKey(toName)
895 re = struct();
896 re.className1 = cn1;
897 re.className2 = cn2;
898 re.fromNode = fromName;
899 re.toNode = toName;
900 re.prob = prob;
901 entries{end+1} = re; %#ok<AGROW>
902 end
903 end
904 end
905 catch
906 % Skip if parsing fails
907 end
908 end
909 end
910end
911end
912
913
914function objStr = extract_json_object(str, startIdx)
915% Extract a JSON object string starting at startIdx (must be '{').
916if str(startIdx) ~= '{'
917 objStr = '';
918 return;
919end
920depth = 0;
921inString = false;
922escaped = false;
923for i = startIdx:length(str)
924 c = str(i);
925 if escaped
926 escaped = false;
927 continue;
928 end
929 if c == '\'
930 escaped = true;
931 continue;
932 end
933 if c == '"'
934 inString = ~inString;
935 continue;
936 end
937 if ~inString
938 if c == '{'
939 depth = depth + 1;
940 elseif c == '}'
941 depth = depth - 1;
942 if depth == 0
943 objStr = str(startIdx:i);
944 return;
945 end
946 end
947 end
948end
949objStr = '';
950end
951
952
953% =========================================================================
954% Helper functions
955% =========================================================================
956
957function id = str_to_sched_id(str)
958% Map scheduling string to SchedStrategy numeric ID.
959switch upper(str)
960 case 'INF', id = SchedStrategy.INF;
961 case 'FCFS', id = SchedStrategy.FCFS;
962 case 'LCFS', id = SchedStrategy.LCFS;
963 case 'LCFSPR', id = SchedStrategy.LCFSPR;
964 case 'PS', id = SchedStrategy.PS;
965 case 'DPS', id = SchedStrategy.DPS;
966 case 'GPS', id = SchedStrategy.GPS;
967 case 'SIRO', id = SchedStrategy.SIRO;
968 case 'RAND', id = SchedStrategy.SIRO; % alias
969 case 'SJF', id = SchedStrategy.SJF;
970 case 'LJF', id = SchedStrategy.LJF;
971 case 'SEPT', id = SchedStrategy.SEPT;
972 case 'LEPT', id = SchedStrategy.LEPT;
973 case 'HOL', id = SchedStrategy.HOL;
974 case 'FCFSPRIO', id = SchedStrategy.FCFSPRIO;
975 case 'FORK', id = SchedStrategy.FORK;
976 case 'EXT', id = SchedStrategy.EXT;
977 case 'REF', id = SchedStrategy.REF;
978 case 'POLLING', id = SchedStrategy.POLLING;
979 case 'PSPRIO', id = SchedStrategy.PSPRIO;
980 case 'DPSPRIO', id = SchedStrategy.DPSPRIO;
981 case 'GPSPRIO', id = SchedStrategy.GPSPRIO;
982 otherwise, id = SchedStrategy.FCFS;
983end
984end
985
986
987function id = str_to_repl_id(str)
988% Map replacement strategy string to ReplacementStrategy numeric ID.
989switch upper(str)
990 case 'LRU', id = ReplacementStrategy.LRU;
991 case 'FIFO', id = ReplacementStrategy.FIFO;
992 case 'RR', id = ReplacementStrategy.RR;
993 case 'SFIFO', id = ReplacementStrategy.SFIFO;
994 otherwise, id = ReplacementStrategy.LRU;
995end
996end
Definition mmt.m:93