1function model = linemodel_load(filename)
2% LINEMODEL_LOAD Load a LINE model from JSON.
4% MODEL = LINEMODEL_LOAD(FILENAME) loads a model from the specified JSON
5% file (conforming to line-model.schema.json) and returns a Network,
6% LayeredNetwork, Workflow, or Environment
object.
9% filename - path to a .json file
12% model - Network, LayeredNetwork, Workflow, or Environment
object
15% model = linemodel_load('mm1.json');
16% solver = SolverMVA(model);
17% AvgTable = solver.getAvgTable();
19% Copyright (c) 2012-2026, Imperial College London
22jsonText = fileread(filename);
23doc = jsondecode(jsonText);
25if ~isfield(doc, 'model')
26 error('linemodel_load:noModel', 'JSON file does not contain a "model" field.');
34 model = json2network(data, jsonText);
36 model = json2layered(data);
38 model = json2workflow(data);
40 model = json2environment(data, jsonText);
42 error('linemodel_load:unknownType', 'Unsupported model type: %s', mtype);
47% =========================================================================
48% Network deserialization
49% =========================================================================
51function model = json2network(data, rawJson)
52% Reconstruct a Network from decoded JSON struct.
53% rawJson
is the original text, used for parsing routing keys with commas.
56if isfield(data, 'name')
57 modelName = data.name;
59model = Network(modelName);
61% --- Create
nodes (before
classes, since ClosedClass needs refstat) ---
63if isfield(data,
'nodes')
77 node = create_node(model, nd, nd_name, nd_type);
78 nodeList{end+1} = node; %#ok<AGROW>
81node_map = containers.Map();
82for i = 1:length(nodeList)
83 node_map(nodeList{i}.name) = nodeList{i};
86% --- Deferred node linking (Fork/Join, Fork tasksPerLink) ---
87if isfield(data,
'nodes')
90 nds2 = num2cell(nds2);
92 for i = 1:length(nds2)
95 node2 = node_map(nd_name2);
96 % Join: link to paired Fork
97 if isfield(nd2,
'forkNode') && isa(node2,
'Join')
98 if node_map.isKey(nd2.forkNode)
99 node2.joinOf = node_map(nd2.forkNode);
102 % Fork: set tasksPerLink
103 if isfield(nd2, 'tasksPerLink') && isa(node2, 'Fork')
104 node2.setTasksPerLink(nd2.tasksPerLink);
111if isfield(data,
'classes')
116 for i = 1:length(cls)
123 if isfield(cd,
'priority'), prio = cd.priority; end
124 jc = OpenClass(model, cname, prio);
128 if isfield(cd,
'refNode') && node_map.isKey(cd.refNode)
129 refNode = node_map(cd.refNode);
132 if isfield(cd,
'priority'), prio = cd.priority; end
134 error(
'linemodel_load:noRefNode', ...
135 'ClosedClass "%s" has no valid refNode.', cname);
137 jc = ClosedClass(model, cname, pop, refNode, prio);
140 if isfield(cd,
'priority'), prio = cd.priority; end
141 sigType = SignalType.NEGATIVE;
142 if isfield(cd,
'signalType')
143 sigType = SignalType.fromText(cd.signalType);
145 openOrClosed = 'Open';
146 if isfield(cd, 'openOrClosed')
147 openOrClosed = cd.openOrClosed;
149 if strcmp(openOrClosed, 'Closed')
151 if isfield(cd, 'refNode') && node_map.isKey(cd.refNode)
152 refNode = node_map(cd.refNode);
155 error('linemodel_load:noRefNode', ...
156 'ClosedSignal "%s" has no valid refNode.', cname);
158 jc = ClosedSignal(model, cname, sigType, refNode, prio);
160 jc = OpenSignal(model, cname, sigType, prio);
162 % Removal distribution
163 if isfield(cd, 'removalDistribution')
164 remDist = json2dist(cd.removalDistribution);
166 jc.setRemovalDistribution(remDist);
170 if isfield(cd, 'removalPolicy')
171 jc.setRemovalPolicy(RemovalPolicy.fromText(cd.removalPolicy));
174 jc = OpenClass(model, cname);
176 if isfield(cd, 'deadline') && isfinite(cd.deadline)
177 jc.deadline = cd.deadline;
179 classesList{end+1} = jc; %#ok<AGROW>
182class_map = containers.Map();
183for i = 1:length(classesList)
184 class_map(classesList{i}.name) = classesList{i};
187% --- Resolve signal targetClass associations ---
188if isfield(data,
'classes')
190 if isstruct(cls2), cls2 = num2cell(cls2); end
191 for i = 1:length(cls2)
193 if isfield(cd2,
'type') && strcmp(cd2.type,
'Signal') && isfield(cd2,
'targetClass')
194 if class_map.isKey(cd2.name) && class_map.isKey(cd2.targetClass)
195 sigCls = class_map(cd2.name);
196 sigCls.forJobClass(class_map(cd2.targetClass));
202% --- Set service/arrival distributions ---
203if isfield(data, '
nodes')
208 for i = 1:length(nds)
211 node = node_map(nd_name);
213 if isfield(nd,
'service') && ~isempty(nd.service)
215 svcFields = fieldnames(svc);
216 for f = 1:length(svcFields)
217 cname = svcFields{f};
218 distJson = svc.(cname);
219 if ~class_map.isKey(cname)
222 jc = class_map(cname);
223 dist = json2dist(distJson);
225 if isa(node,
'Source')
226 node.setArrival(jc, dist);
227 elseif isa(node, 'Queue') || isa(node, 'Delay')
228 % Pass DPS/GPS weight if available
230 if isfield(nd, 'schedParams')
231 sp_tmp = nd.schedParams;
232 if isfield(sp_tmp, cname)
233 weight = sp_tmp.(cname);
236 node.setService(jc, dist, weight);
242 % ClassSwitch matrix (dict format: classSwitchMatrix)
243 if isfield(nd, 'classSwitchMatrix') && isa(node, 'ClassSwitch')
244 csm_data = nd.classSwitchMatrix;
248 class_idx = containers.Map();
250 class_idx(
classes{ci}.name) = ci;
252 fromFields = fieldnames(csm_data);
253 for fi = 1:length(fromFields)
254 fromName = fromFields{fi};
255 if ~class_idx.isKey(fromName),
continue; end
256 ri = class_idx(fromName);
257 toStruct = csm_data.(fromName);
258 toFields = fieldnames(toStruct);
259 for ti = 1:length(toFields)
260 toName = toFields{ti};
261 if ~class_idx.isKey(toName),
continue; end
262 ci = class_idx(toName);
263 mat(ri, ci) = toStruct.(toName);
266 node.server = node.server.updateClassSwitch(mat);
267 % Legacy 2D
array format: csMatrix (from older JAR saves)
268 elseif isfield(nd,
'csMatrix') && isa(node,
'ClassSwitch')
273 node.server = node.server.updateClassSwitch(mat);
276 % DPS scheduling parameters (weights already passed via setService above)
278 % Per-class buffer capacity
279 if isfield(nd, 'classCap') && isa(node, 'Queue')
280 ccData = nd.classCap;
281 ccFields = fieldnames(ccData);
282 for cci = 1:length(ccFields)
283 cname = ccFields{cci};
284 if class_map.isKey(cname)
285 jc = class_map(cname);
287 cls = model.getClasses();
288 for ci = 1:length(cls)
289 if strcmp(cls{ci}.name, cname)
290 node.classCap(ci) = ccData.(cname);
299 if isfield(nd,
'dropRule') && isa(node,
'Queue')
300 drData = nd.dropRule;
301 drFields = fieldnames(drData);
302 for dri = 1:length(drFields)
303 cname = drFields{dri};
304 if class_map.isKey(cname)
305 cls = model.getClasses();
306 for ci = 1:length(cls)
307 if strcmp(cls{ci}.name, cname)
308 node.dropRule(ci) = str_to_droprule(drData.(cname));
316 % Load-dependent scaling
317 if isfield(nd,
'loadDependence') && isa(node,
'Queue')
318 ld = nd.loadDependence;
319 if isfield(ld, 'type') && strcmp(ld.type, 'loadDependent') && isfield(ld, 'scaling')
320 scaling = ld.scaling(:)';
321 node.setLoadDependence(scaling);
325 % Join strategy and quorum
327 if isfield(nd, 'joinStrategy')
328 jsStr = nd.joinStrategy;
333 node.input.setStrategy(
classes{ci}, JoinStrategy.STD);
334 case {
'PARTIAL',
'QUORUM',
'Quorum'}
335 node.input.setStrategy(
classes{ci}, JoinStrategy.PARTIAL);
339 if isfield(nd,
'joinQuorum')
343 node.input.setRequired(
classes{ci}, jq);
348 % Cache hit/miss
class mappings and popularity distributions
349 if isa(node, 'Cache') && isfield(nd,
'cache')
352 if isfield(cc, 'hitClass')
353 hcData = cc.hitClass;
354 hcFields = fieldnames(hcData);
355 for hci = 1:length(hcFields)
356 inName = hcFields{hci};
357 outName = hcData.(inName);
358 if class_map.isKey(inName) && class_map.isKey(outName)
359 node.setHitClass(class_map(inName), class_map(outName));
364 if isfield(cc, 'missClass')
365 mcData = cc.missClass;
366 mcFields = fieldnames(mcData);
367 for mci = 1:length(mcFields)
368 inName = mcFields{mci};
369 outName = mcData.(inName);
370 if class_map.isKey(inName) && class_map.isKey(outName)
371 node.setMissClass(class_map(inName), class_map(outName));
375 % Popularity distributions (setRead)
376 if isfield(cc,
'popularity')
377 popData = cc.popularity;
378 popFields = fieldnames(popData);
379 for pfi = 1:length(popFields)
380 cname = popFields{pfi};
381 if class_map.isKey(cname)
382 popDist = json2dist(popData.(cname));
383 if ~isempty(popDist) && ~isa(popDist,
'Disabled')
384 node.setRead(class_map(cname), popDist);
390 % Heterogeneous server types
391 if isa(node, 'Queue') && isfield(nd, 'serverTypes')
392 stArr = nd.serverTypes;
393 if isstruct(stArr), stArr = num2cell(stArr); end
394 for si = 1:length(stArr)
396 stName = stData.name;
397 stCount = stData.count;
398 st = ServerType(stName, stCount);
400 if isfield(stData,
'compatibleClasses')
401 ccList = stData.compatibleClasses;
402 if ~iscell(ccList), ccList = {ccList}; end
403 for cci = 1:length(ccList)
404 if class_map.isKey(ccList{cci})
405 st.addCompatible(class_map(ccList{cci}));
409 node.addServerType(st);
410 % Per-
class service distributions
411 if isfield(stData, 'service')
412 svcData = stData.service;
413 svcFields = fieldnames(svcData);
414 for fi = 1:length(svcFields)
415 cname = svcFields{fi};
416 if class_map.isKey(cname)
417 jc = class_map(cname);
418 dist = json2dist(svcData.(cname));
420 node.setHeteroService(jc, st, dist);
427 if isfield(nd,
'heteroSchedPolicy')
428 policy = HeteroSchedPolicy.fromText(nd.heteroSchedPolicy);
429 node.setHeteroSchedPolicy(policy);
435% --- Restore Balking, Retrial, Patience ---
436if isfield(data, '
nodes')
438 if isstruct(ndsImp), ndsImp = num2cell(ndsImp); end
439 for i = 1:length(ndsImp)
441 if ~node_map.isKey(ndImp.name),
continue; end
442 node = node_map(ndImp.name);
443 if ~isa(node,
'Queue'),
continue; end
445 if isfield(ndImp,
'balking') && ~isempty(ndImp.balking)
446 balkData = ndImp.balking;
447 fnames = fieldnames(balkData);
448 for fi = 1:length(fnames)
449 className = fnames{fi};
450 if ~class_map.isKey(className),
continue; end
451 jc = class_map(className);
452 bjc = balkData.(className);
455 case 'QUEUE_LENGTH', strategy = BalkingStrategy.QUEUE_LENGTH;
456 case 'EXPECTED_WAIT', strategy = BalkingStrategy.EXPECTED_WAIT;
457 case 'COMBINED', strategy = BalkingStrategy.COMBINED;
461 thData = bjc.thresholds;
462 if isstruct(thData), thData = num2cell(thData); end
464 for ti = 1:length(thData)
466 maxJobs = td.maxJobs;
467 if maxJobs < 0, maxJobs = Inf; end
468 thresholds{end+1} = {td.minJobs, maxJobs, td.probability};
470 node.setBalking(jc, strategy, thresholds);
474 if isfield(ndImp,
'retrial') && ~isempty(ndImp.retrial)
475 retData = ndImp.retrial;
476 fnames = fieldnames(retData);
477 for fi = 1:length(fnames)
478 className = fnames{fi};
479 if ~class_map.isKey(className),
continue; end
480 jc = class_map(className);
481 rjc = retData.(className);
482 delayDist = json2dist(rjc.delay);
484 if isfield(rjc,
'maxAttempts')
485 maxAttempts = rjc.maxAttempts;
487 node.setRetrial(jc, delayDist, maxAttempts);
491 if isfield(ndImp, 'patience') && ~isempty(ndImp.patience)
492 patData = ndImp.patience;
493 fnames = fieldnames(patData);
494 for fi = 1:length(fnames)
495 className = fnames{fi};
496 if ~class_map.isKey(className),
continue; end
497 jc = class_map(className);
498 pjc = patData.(className);
499 patDist = json2dist(pjc.distribution);
500 if isfield(pjc,
'impatienceType')
501 switch pjc.impatienceType
502 case 'reneging', impType = ImpatienceType.RENEGING;
503 case 'balking', impType = ImpatienceType.BALKING;
504 case 'retrial', impType = ImpatienceType.RETRIAL;
505 otherwise, impType = ImpatienceType.RENEGING;
508 impType = ImpatienceType.RENEGING;
510 node.setPatience(jc, impType, patDist);
516% --- Configure Transition modes ---
517if isfield(data, '
nodes')
520 nds3 = num2cell(nds3);
522 for i = 1:length(nds3)
524 if ~isfield(nd3,
'modes'),
continue; end
525 if ~strcmp(nd3.type,
'Transition'),
continue; end
526 tnode = node_map(nd3.name);
527 modesData = nd3.modes;
528 if isstruct(modesData)
529 modesData = num2cell(modesData);
531 for mi = 1:length(modesData)
534 if isfield(md,
'name'), modeName = md.name; end
535 mode = tnode.addMode(modeName);
537 if isfield(md,
'distribution') && ~isempty(md.distribution)
538 dist = json2dist(md.distribution);
540 tnode.setDistribution(mode, dist);
544 if isfield(md, 'timingStrategy')
545 if strcmp(md.timingStrategy, 'IMMEDIATE')
546 tnode.setTimingStrategy(mode, TimingStrategy.IMMEDIATE);
548 tnode.setTimingStrategy(mode, TimingStrategy.TIMED);
552 if isfield(md, 'numServers')
553 nsVal = md.numServers;
554 if ischar(nsVal) || isstring(nsVal)
555 if strcmpi(nsVal, 'Infinity'), nsVal = Inf; else, nsVal = str2double(nsVal); end
558 tnode.setNumberOfServers(mode, nsVal);
562 if isfield(md, 'firingPriority')
563 tnode.setFiringPriorities(mode, md.firingPriority);
566 if isfield(md, 'firingWeight')
567 tnode.setFiringWeights(mode, md.firingWeight);
569 % Enabling conditions
570 if isfield(md, 'enablingConditions')
571 ecList = md.enablingConditions;
572 if isstruct(ecList), ecList = num2cell(ecList); end
573 for ei = 1:length(ecList)
575 if node_map.isKey(ec.node) && class_map.isKey(ec.class)
576 tnode.setEnablingConditions(mode, class_map(ec.class), node_map(ec.node), ec.count);
580 % Inhibiting conditions
581 if isfield(md,
'inhibitingConditions')
582 icList = md.inhibitingConditions;
583 if isstruct(icList), icList = num2cell(icList); end
584 for ii = 1:length(icList)
586 if node_map.isKey(ic.node) && class_map.isKey(ic.class)
587 tnode.setInhibitingConditions(mode, class_map(ic.class), node_map(ic.node), ic.count);
592 if isfield(md,
'firingOutcomes')
593 foList = md.firingOutcomes;
594 if isstruct(foList), foList = num2cell(foList); end
595 for fi = 1:length(foList)
597 if node_map.isKey(fo.node) && class_map.isKey(fo.class)
598 tnode.setFiringOutcome(mode, class_map(fo.class), node_map(fo.node), fo.count);
606% --- Restore initial state
for Place
nodes ---
607if isfield(data,
'nodes')
609 if isstruct(nds4), nds4 = num2cell(nds4); end
610 for i = 1:length(nds4)
612 if isfield(nd4,
'initialState') && node_map.isKey(nd4.name)
613 nodeObj = node_map(nd4.name);
614 if isa(nodeObj,
'Place')
615 stVal = nd4.initialState;
616 stVal = stVal(:)'; % Ensure row vector (jsondecode returns column vectors)
617 nodeObj.setState(stVal);
623% --- Build routing ---
624if isfield(data, 'routing') && isfield(data.routing, 'type') && strcmp(data.routing.type, 'matrix')
625 P = model.initRoutingMatrix();
626 K = length(classesList);
627 M = length(nodeList);
629 % Build node/class index maps
630 nodeIdx = containers.Map();
632 nodeIdx(nodeList{i}.name) = i;
634 classIdx = containers.Map();
636 classIdx(classesList{i}.name) = i;
639 % Parse routing keys from raw JSON to preserve commas
640 routingEntries = parse_routing_keys(rawJson, class_map, node_map);
642 for e = 1:length(routingEntries)
643 re = routingEntries{e};
644 r = classIdx(re.className1);
645 s = classIdx(re.className2);
646 ii = nodeIdx(re.fromNode);
647 jj = nodeIdx(re.toNode);
648 P{r,s}(ii, jj) = re.prob;
654% --- Restore routing strategies ---
655if isfield(data,
'routingStrategies')
656 stratMap = containers.Map();
657 stratMap('RAND') = RoutingStrategy.RAND;
658 stratMap('RROBIN') = RoutingStrategy.RROBIN;
659 stratMap('WRROBIN') = RoutingStrategy.WRROBIN;
660 stratMap('JSQ') = RoutingStrategy.JSQ;
661 stratMap('KCHOICES') = RoutingStrategy.KCHOICES;
662 stratMap('FIRING') = RoutingStrategy.FIRING;
663 stratMap('RL') = RoutingStrategy.RL;
664 stratMap('DISABLED') = RoutingStrategy.DISABLED;
666 rsFields = fieldnames(data.routingStrategies);
667 for fi = 1:length(rsFields)
668 nodeName = rsFields{fi};
669 if node_map.isKey(nodeName)
670 nodeObj = node_map(nodeName);
671 classStrats = data.routingStrategies.(nodeName);
672 csFields = fieldnames(classStrats);
673 for ci = 1:length(csFields)
674 className = csFields{ci};
675 stratName = classStrats.(className);
676 if class_map.isKey(className) && stratMap.isKey(stratName)
677 % Skip RAND, PROB: already handled by routing matrix
678 % Skip WRROBIN: handled separately in routingWeights section
679 rs = stratMap(stratName);
680 if rs ~= RoutingStrategy.RAND && rs ~= RoutingStrategy.PROB && rs ~= RoutingStrategy.WRROBIN
681 nodeObj.setRouting(class_map(className), rs);
689% --- Restore routing weights (WRROBIN) ---
690if isfield(data,
'routingWeights')
691 rwFields = fieldnames(data.routingWeights);
692 for fi = 1:length(rwFields)
693 nodeName = rwFields{fi};
694 if node_map.isKey(nodeName)
695 nodeObj = node_map(nodeName);
696 classWeights = data.routingWeights.(nodeName);
697 cwFields = fieldnames(classWeights);
698 for ci = 1:length(cwFields)
699 className = cwFields{ci};
700 destWeights = classWeights.(className);
701 if class_map.isKey(className)
702 % Clear existing routing entries
for this class
703 % (link() may have set PROB entries that would accumulate)
704 classIdx = class_map(className).index;
705 if length(nodeObj.output.outputStrategy) >= classIdx && ...
706 length(nodeObj.output.outputStrategy{1, classIdx}) >= 3
707 nodeObj.output.outputStrategy{1, classIdx}{3} = {};
709 dwFields = fieldnames(destWeights);
710 for di = 1:length(dwFields)
711 destName = dwFields{di};
712 weight = destWeights.(destName);
713 if node_map.isKey(destName)
714 nodeObj.setRouting(class_map(className), RoutingStrategy.WRROBIN, node_map(destName), weight);
723% --- Restore switchover times ---
724for ni = 1:length(data.nodes)
726 if isfield(nd, 'switchoverTimes') && ~isempty(nd.switchoverTimes)
727 nodeObj = node_map(nd.name);
728 soArr = nd.switchoverTimes;
732 for si = 1:length(soArr)
734 fromCls = class_map(so.from);
735 toCls = class_map(so.to);
736 dist = json2dist(so.distribution);
738 nodeObj.setSwitchover(fromCls, toCls, dist);
744% --- Restore finite capacity regions ---
745if isfield(data,
'finiteCapacityRegions')
746 fcrArr = data.finiteCapacityRegions;
751 for ri = 1:length(fcrArr)
754 % Support both old format (
"nodes" list) and
new format (
"stations" array)
755 if isfield(rj,
'stations')
757 if ~iscell(stArr), stArr = {stArr}; end
758 for si = 1:length(stArr)
761 if node_map.isKey(nodeName)
762 regNodes{end+1} = node_map(nodeName); %#ok<AGROW>
765 elseif isfield(rj,
'nodes')
766 nodeNames = rj.
nodes;
767 if ~iscell(nodeNames), nodeNames = {nodeNames}; end
768 for ni = 1:length(nodeNames)
769 if node_map.isKey(nodeNames{ni})
770 regNodes{end+1} = node_map(nodeNames{ni}); %#ok<AGROW>
774 maxJobs = FiniteCapacityRegion.UNBOUNDED;
775 if isfield(rj,
'globalMaxJobs')
776 maxJobs = rj.globalMaxJobs;
778 if ~isempty(regNodes)
780 region = model.addRegion(rj.name, regNodes, maxJobs);
782 if isfield(rj, 'globalMaxMemory')
783 region.globalMaxMemory = rj.globalMaxMemory;
786 if isfield(rj, 'classMaxJobs')
787 cmj = rj.classMaxJobs;
788 cmjFields = fieldnames(cmj);
789 for ci = 1:length(cmjFields)
790 cname = cmjFields{ci};
791 if class_map.isKey(cname)
792 jc = class_map(cname);
793 region.classMaxJobs(jc.index) = cmj.(cname);
798 if isfield(rj,
'dropRule')
799 drData = rj.dropRule;
800 drFields = fieldnames(drData);
801 for di = 1:length(drFields)
802 cname = drFields{di};
803 if class_map.isKey(cname)
804 jc = class_map(cname);
805 region.dropRule(jc.index) = str_to_droprule(drData.(cname));
809 % Per-station classWeight and classSize from stations
array
810 if isfield(rj,
'stations')
811 stArr2 = rj.stations;
812 if ~iscell(stArr2), stArr2 = {stArr2}; end
813 for si = 1:length(stArr2)
815 if isfield(sj2,
'classWeight')
816 cwData = sj2.classWeight;
817 cwFields = fieldnames(cwData);
818 for ci = 1:length(cwFields)
819 cname = cwFields{ci};
820 if class_map.isKey(cname)
821 jc = class_map(cname);
822 region.classWeight(jc.index) = cwData.(cname);
826 if isfield(sj2,
'classSize')
827 csData = sj2.classSize;
828 csFields = fieldnames(csData);
829 for ci = 1:length(csFields)
830 cname = csFields{ci};
831 if class_map.isKey(cname)
832 jc = class_map(cname);
833 region.classSize(jc.index) = csData.(cname);
847function node = create_node(model, nd, name, ntype)
848% Create a node from JSON data.
851 node = Source(model, name);
853 node = Sink(model, name);
855 node = Delay(model, name);
858 if isfield(nd,
'scheduling')
859 schedStr = nd.scheduling;
861 schedId = str_to_sched_id(schedStr);
862 node = Queue(model, name, schedId);
863 if isfield(nd, 'servers') && nd.servers > 1
864 node.setNumberOfServers(nd.servers);
866 if isfield(nd, 'buffer') && isfinite(nd.buffer)
867 node.cap = nd.buffer;
870 node = Fork(model, name);
872 node = Join(model, name);
874 node = Router(model, name);
876 node = ClassSwitch(model, name);
879 if isfield(nd, 'cache')
883 if isfield(cc, 'items'), nitems = cc.items; end
885 if isfield(cc, 'capacity'), cap = cc.capacity; end
887 if isfield(cc, 'replacement'), replStr = cc.replacement; end
888 replId = str_to_repl_id(replStr);
889 node = Cache(model, name, nitems, cap, replId);
891 node = Place(model, name);
893 node = Transition(model, name);
895 node = Queue(model, name, SchedStrategy.FCFS);
900% =========================================================================
901% LayeredNetwork deserialization
902% =========================================================================
904function model = json2layered(data)
905% Reconstruct a LayeredNetwork from decoded JSON struct.
908if isfield(data, 'name')
909 modelName = data.name;
911model = LayeredNetwork(modelName);
913% --- Processors (Python schema: "processors", JAR schema: "hosts") ---
914proc_map = containers.Map();
915if isfield(data, 'processors')
916 procs = data.processors;
917elseif isfield(data, 'hosts')
923 if isstruct(procs), procs = num2cell(procs); end
924 for i = 1:length(procs)
928 if isfield(pd,
'multiplicity'), mult = pd.multiplicity; end
930 if isfield(pd,
'scheduling'), schedStr = pd.scheduling; end
931 schedId = str_to_sched_id(schedStr);
933 if isfield(pd,
'quantum'), quantum = pd.quantum; end
935 if isfield(pd,
'speedFactor'), sf = pd.speedFactor; end
936 proc = Host(model, pname, mult, schedId, quantum, sf);
937 if isfield(pd,
'replication') && pd.replication > 1
938 proc.setReplication(pd.replication);
940 proc_map(pname) = proc;
945task_map = containers.Map();
946if isfield(data,
'tasks')
948 if isstruct(tsks), tsks = num2cell(tsks); end
949 for i = 1:length(tsks)
953 if isfield(td,
'multiplicity'), mult = td.multiplicity; end
955 if isfield(td,
'scheduling'), schedStr = td.scheduling; end
956 schedId = str_to_sched_id(schedStr);
958 if isfield(td,
'taskType'), taskType = td.taskType; end
959 if strcmp(taskType,
'FunctionTask')
960 task = FunctionTask(model, tname, mult, schedId);
961 elseif strcmp(taskType, 'CacheTask')
963 if isfield(td, 'totalItems'), totalItems = td.totalItems; end
965 if isfield(td, 'cacheCapacity'), cacheCap = td.cacheCapacity; end
967 if isfield(td, 'replacementStrategy'), rsStr = td.replacementStrategy; end
968 rsMap = containers.Map({
'RR',
'FIFO',
'SFIFO',
'LRU'}, ...
969 {ReplacementStrategy.RR, ReplacementStrategy.FIFO, ...
970 ReplacementStrategy.SFIFO, ReplacementStrategy.LRU});
971 if rsMap.isKey(upper(rsStr))
972 rs = rsMap(upper(rsStr));
974 rs = ReplacementStrategy.FIFO;
976 task = CacheTask(model, tname, totalItems, cacheCap, rs, mult, schedId);
978 task = Task(model, tname, mult, schedId);
980 % Assign to processor (Python schema:
"processor", JAR schema:
"host")
982 if isfield(td,
'processor'), procRef = td.processor;
983 elseif isfield(td,
'host'), procRef = td.host;
985 if ~isempty(procRef) && proc_map.isKey(procRef)
986 task.on(proc_map(procRef));
988 % Think time (Python schema:
"thinkTime" as dist, JAR schema:
"thinkTimeMean"/
"thinkTimeSCV")
989 if isfield(td,
'thinkTime')
990 dist = json2dist(td.thinkTime);
992 task.setThinkTime(dist);
994 elseif isfield(td,
'thinkTimeMean') && td.thinkTimeMean > 0
995 task.setThinkTime(Exp(1.0 / td.thinkTimeMean));
998 if isfield(td,
'setupTime')
999 dist = json2dist(td.setupTime);
1001 task.setSetupTime(dist);
1003 elseif isfield(td, 'setupTimeMean') && td.setupTimeMean > 1e-8
1004 task.setSetupTime(Exp(1.0 / td.setupTimeMean));
1007 if isfield(td, 'delayOffTime')
1008 dist = json2dist(td.delayOffTime);
1010 task.setDelayOffTime(dist);
1012 elseif isfield(td, 'delayOffTimeMean') && td.delayOffTimeMean > 1e-8
1013 task.setDelayOffTime(Exp(1.0 / td.delayOffTimeMean));
1016 if isfield(td, 'fanIn') && isstruct(td.fanIn)
1017 fnames = fieldnames(td.fanIn);
1018 for fi = 1:length(fnames)
1019 task.setFanIn(fnames{fi}, td.fanIn.(fnames{fi}));
1023 if isfield(td,
'fanOut') && isstruct(td.fanOut)
1024 fnames = fieldnames(td.fanOut);
1025 for fi = 1:length(fnames)
1026 task.setFanOut(fnames{fi}, td.fanOut.(fnames{fi}));
1030 if isfield(td,
'replication') && td.replication > 1
1031 task.setReplication(td.replication);
1033 task_map(tname) = task;
1038entry_map = containers.Map();
1039if isfield(data,
'entries')
1040 ents = data.entries;
1041 if isstruct(ents), ents = num2cell(ents); end
1042 for i = 1:length(ents)
1045 entryType =
'Entry';
1046 if isfield(ed,
'entryType'), entryType = ed.entryType; end
1047 if strcmp(entryType,
'ItemEntry')
1049 if isfield(ed, 'totalItems'), totalItems = ed.totalItems; end
1051 if isfield(ed, 'accessProb')
1054 accessProb = json2dist(ap);
1055 elseif isnumeric(ap)
1056 accessProb = DiscreteSampler(ap);
1059 if isempty(accessProb)
1060 % Default uniform distribution
1061 accessProb = DiscreteSampler(ones(1, totalItems) / totalItems);
1063 entry = ItemEntry(model, ename, totalItems, accessProb);
1065 entry = Entry(model, ename);
1067 if isfield(ed, 'task') && task_map.isKey(ed.task)
1068 entry.on(task_map(ed.task));
1070 % Entry arrival distribution
1071 if isfield(ed, 'arrival')
1072 dist = json2dist(ed.arrival);
1074 entry.setArrival(dist);
1077 entry_map(ename) = entry;
1082act_map = containers.Map();
1083if isfield(data, 'activities')
1084 acts = data.activities;
1085 if isstruct(acts), acts = num2cell(acts); end
1086 for i = 1:length(acts)
1091 hd = GlobalConstants.FineTol;
1092 if isfield(ad,
'hostDemand')
1093 hdDist = json2dist(ad.hostDemand);
1099 % Bound to entry (Python schema: "boundTo", JAR schema: "boundToEntry")
1101 if isfield(ad, 'boundTo')
1103 elseif isfield(ad, 'boundToEntry')
1104 bte = ad.boundToEntry;
1107 act = Activity(model, aname, hd, bte);
1110 if isfield(ad, 'task') && task_map.isKey(ad.task)
1111 act.on(task_map(ad.task));
1115 if isfield(ad, 'repliesTo') && entry_map.isKey(ad.repliesTo)
1116 act.repliesTo(entry_map(ad.repliesTo));
1119 % Synch calls (Python schema: "entry", JAR schema: "dest")
1120 if isfield(ad, 'synchCalls')
1121 scs = ad.synchCalls;
1122 if isstruct(scs), scs = num2cell(scs); end
1123 for j = 1:length(scs)
1125 if isfield(sc,
'entry'), ename = sc.entry;
1126 elseif isfield(sc,
'dest'), ename = sc.dest;
1130 if isfield(sc,
'mean'), meanCalls = sc.mean; end
1131 if entry_map.isKey(ename)
1132 act.synchCall(entry_map(ename), meanCalls);
1137 % Asynch calls (Python schema:
"entry", JAR schema:
"dest")
1138 if isfield(ad,
'asynchCalls')
1139 acs = ad.asynchCalls;
1140 if isstruct(acs), acs = num2cell(acs); end
1141 for j = 1:length(acs)
1143 if isfield(ac,
'entry'), ename = ac.entry;
1144 elseif isfield(ac,
'dest'), ename = ac.dest;
1148 if isfield(ac,
'mean'), meanCalls = ac.mean; end
1149 if entry_map.isKey(ename)
1150 act.asynchCall(entry_map(ename), meanCalls);
1155 act_map(aname) = act;
1159% --- Precedences (Python schema:
"type"/
"activities", JAR schema:
"preActs"/
"postActs"/
"preType"/
"postType") ---
1160if isfield(data,
'precedences')
1161 precs = data.precedences;
1162 if isstruct(precs), precs = num2cell(precs); end
1163 for i = 1:length(precs)
1165 if ~isfield(pd,
'task') || ~task_map.isKey(pd.task)
1168 task = task_map(pd.task);
1170 if isfield(pd,
'preActs') || isfield(pd,
'postActs')
1174 if isfield(pd,
'preActs')
1175 preNames = pd.preActs;
1176 if ischar(preNames), preNames = {preNames}; end
1178 if isfield(pd,
'postActs')
1179 postNames = pd.postActs;
1180 if ischar(postNames), postNames = {postNames}; end
1184 if isfield(pd,
'preType'), preType = pd.preType; end
1185 if isfield(pd,
'postType'), postType = pd.postType; end
1187 % Normalize JAR naming convention to Python convention
1189 case 'post-AND', postType =
'and-fork';
1190 case 'post-OR', postType =
'or-fork';
1191 case 'post-LOOP', postType =
'loop';
1194 case 'pre-AND', preType =
'and-join';
1195 case 'pre-OR', preType =
'or-join';
1198 % Extract postParams (JAR schema: probabilities/loopCount)
1200 if isfield(pd,
'postParams')
1201 postParams = pd.postParams;
1202 if iscell(postParams), postParams = cell2mat(postParams); end
1206 for ai = 1:length(preNames)
1207 if act_map.isKey(preNames{ai})
1208 preActs{end+1} = act_map(preNames{ai}); %#ok<AGROW>
1212 for ai = 1:length(postNames)
1213 if act_map.isKey(postNames{ai})
1214 postActs{end+1} = act_map(postNames{ai}); %#ok<AGROW>
1218 if strcmp(preType,
'pre') && strcmp(postType,
'post')
1219 if length(preActs) == 1 && length(postActs) == 1
1220 ap = ActivityPrecedence.Serial(preActs{1}, postActs{1});
1221 task.addPrecedence(ap);
1223 elseif strcmp(preType,
'pre') && strcmp(postType,
'and-fork')
1224 if ~isempty(preActs) && ~isempty(postActs)
1225 ap = ActivityPrecedence.AndFork(preActs{1}, postActs);
1226 task.addPrecedence(ap);
1228 elseif strcmp(preType,
'and-join') && strcmp(postType,
'post')
1229 if ~isempty(preActs) && ~isempty(postActs)
1230 ap = ActivityPrecedence.AndJoin(preActs, postActs{1});
1231 task.addPrecedence(ap);
1233 elseif strcmp(preType,
'pre') && strcmp(postType,
'or-fork')
1234 if ~isempty(preActs) && ~isempty(postActs)
1236 if isfield(pd, 'probabilities')
1237 probs = pd.probabilities;
1238 if isstruct(probs), probs = cell2mat(struct2cell(probs)); end
1240 if isempty(probs) && ~isempty(postParams)
1241 probs = postParams(:)';
1244 n = length(postActs);
1245 probs = ones(1, n) / n;
1247 ap = ActivityPrecedence.OrFork(preActs{1}, postActs, probs);
1248 task.addPrecedence(ap);
1250 elseif strcmp(preType,
'or-join') && strcmp(postType,
'post')
1251 if ~isempty(preActs) && ~isempty(postActs)
1252 ap = ActivityPrecedence.OrJoin(preActs, postActs{1});
1253 task.addPrecedence(ap);
1255 elseif strcmp(preType,
'pre') && strcmp(postType,
'loop')
1257 if isfield(pd, 'loopCount'), count = pd.loopCount; end
1258 if count == 1.0 && ~isempty(postParams)
1259 count = postParams(1);
1261 if ~isempty(preActs) && ~isempty(postActs)
1262 if length(postActs) > 1
1263 ap = ActivityPrecedence.Loop(preActs{1}, postActs(1:end-1), postActs{end}, count);
1265 ap = ActivityPrecedence.Loop(preActs{1}, postActs, count);
1267 task.addPrecedence(ap);
1269 elseif strcmp(preType,
'pre') && strcmp(postType,
'post-CACHE')
1270 if ~isempty(preActs) && ~isempty(postActs)
1271 ap = ActivityPrecedence.CacheAccess(preActs{1}, postActs);
1272 task.addPrecedence(ap);
1278 actNames = pd.activities;
1279 if ischar(actNames), actNames = {actNames}; end
1281 % Resolve activity names to objects
1283 for ai = 1:length(actNames)
1285 if act_map.isKey(an)
1286 actObjs{end+1} = act_map(an); %#ok<AGROW>
1289 if length(actObjs) < 2
1295 ap = ActivityPrecedence.Serial(actObjs{:});
1296 task.addPrecedence(ap);
1298 ap = ActivityPrecedence.AndFork(actObjs{1}, actObjs(2:end));
1299 task.addPrecedence(ap);
1301 ap = ActivityPrecedence.AndJoin(actObjs(1:end-1), actObjs{end});
1302 task.addPrecedence(ap);
1305 if isfield(pd,
'probabilities')
1306 probs = pd.probabilities;
1307 if isstruct(probs), probs = cell2mat(struct2cell(probs)); end
1310 n = length(actObjs) - 1;
1311 probs = ones(1, n) / n;
1313 ap = ActivityPrecedence.OrFork(actObjs{1}, actObjs(2:end), probs);
1314 task.addPrecedence(ap);
1316 ap = ActivityPrecedence.OrJoin(actObjs(1:end-1), actObjs{end});
1317 task.addPrecedence(ap);
1320 if isfield(pd,
'loopCount'), count = pd.loopCount; end
1321 % Check
for explicit preActivity field (
new format)
1322 if isfield(pd,
'preActivity') && act_map.isKey(pd.preActivity)
1323 preAct = act_map(pd.preActivity);
1324 ap = ActivityPrecedence.Loop(preAct, actObjs, count);
1325 elseif length(actObjs) >= 3
1326 % Legacy format: first
is pre, rest
is body+end
1327 ap = ActivityPrecedence.Loop(actObjs{1}, actObjs(2:end-1), actObjs{end}, count);
1329 ap = ActivityPrecedence.Loop(actObjs{1}, actObjs(2:end), count);
1331 task.addPrecedence(ap);
1333 if length(actObjs) >= 2
1334 ap = ActivityPrecedence.CacheAccess(actObjs{1}, actObjs(2:end));
1335 task.addPrecedence(ap);
1344% =========================================================================
1345% Workflow deserialization
1346% =========================================================================
1348function model = json2workflow(data)
1349% Reconstruct a Workflow from decoded JSON
struct.
1351modelName =
'workflow';
1352if isfield(data,
'name')
1353 modelName = data.name;
1355model = Workflow(modelName);
1358if isfield(data, 'activities')
1359 acts = data.activities;
1360 if isstruct(acts), acts = num2cell(acts); end
1361 for i = 1:length(acts)
1364 if isfield(ad,
'hostDemand') && ~isempty(ad.hostDemand)
1365 dist = json2dist(ad.hostDemand);
1366 model.addActivity(actName, dist);
1368 model.addActivity(actName, 1.0);
1373% --- Precedences ---
1374if isfield(data, 'precedences')
1375 precs = data.precedences;
1376 if isstruct(precs), precs = num2cell(precs); end
1377 for i = 1:length(precs)
1381 if isfield(pd,
'preActs')
1382 preActs = cellify_string_array(pd.preActs);
1388 if isfield(pd,
'postActs')
1389 postActs = cellify_string_array(pd.postActs);
1394 % preType / postType - convert JAR strings to numeric IDs
1395 preType = ActivityPrecedenceType.PRE_SEQ;
1396 if isfield(pd,
'preType')
1397 preType = str_to_prectype(pd.preType);
1399 postType = ActivityPrecedenceType.POST_SEQ;
1400 if isfield(pd, 'postType')
1401 postType = str_to_prectype(pd.postType);
1404 % preParams / postParams
1406 if isfield(pd, 'preParams') && ~isempty(pd.preParams)
1407 preParams = pd.preParams(:)';
1410 if isfield(pd, 'postParams') && ~isempty(pd.postParams)
1411 postParams = pd.postParams(:)';
1414 ap = ActivityPrecedence(preActs, postActs, preType, postType, preParams, postParams);
1415 model.addPrecedence(ap);
1421% =========================================================================
1422% Environment deserialization
1423% =========================================================================
1425function model = json2environment(data, rawJson)
1426% Reconstruct an Environment from decoded JSON struct.
1429if isfield(data, 'name')
1430 modelName = data.name;
1433if isfield(data, 'numStages')
1434 numStages = data.numStages;
1436model = Environment(modelName, numStages);
1440if isfield(data,
'stages')
1441 stages = data.stages;
1442 if isstruct(stages), stages = num2cell(stages); end
1443 for i = 1:length(stages)
1445 stageName = sprintf(
'Stage%d', i);
1446 if isfield(sd,
'name')
1447 stageName = sd.name;
1449 stageNames{end+1} = stageName; %#ok<AGROW>
1452 if isfield(sd,
'type')
1453 stageType = sd.type;
1457 if isfield(sd, 'model') && ~isempty(sd.model)
1458 stageModel = json2network(sd.model, rawJson);
1461 if ~isempty(stageModel)
1462 model.addStage(stageName, stageType, stageModel);
1467% --- Transitions ---
1468if isfield(data, 'transitions')
1469 trans = data.transitions;
1470 if isstruct(trans), trans = num2cell(trans); end
1471 for i = 1:length(trans)
1473 fromIdx = td.from + 1; % Convert from 0-indexed (JAR) to 1-indexed (MATLAB)
1474 toIdx = td.to + 1; % Convert from 0-indexed (JAR) to 1-indexed (MATLAB)
1475 if isfield(td,
'distribution') && ~isempty(td.distribution)
1476 dist = json2dist(td.distribution);
1477 if ~isempty(dist) && ~isa(dist, 'Disabled')
1478 % Use stage names for MATLAB Environment API
1479 if fromIdx <= length(stageNames) && toIdx <= length(stageNames)
1480 model.addTransition(stageNames{fromIdx}, stageNames{toIdx}, dist);
1491% =========================================================================
1492% Distribution deserialization
1493% =========================================================================
1495function dist = json2dist(d)
1496% Convert a JSON distribution
struct to a MATLAB Distribution object.
1506 dist = Disabled.getInstance();
1509 dist = Immediate.getInstance();
1514if isfield(d,
'params') && ~isempty(d.params)
1522 dist = Det(p.value);
1525 dist = Erlang(p.lambda, p.k);
1531 dist = HyperExp(pv, lv(1), lv(2));
1533 dist = HyperExp(pv(1), lv(1), lv(2));
1537 dist = Gamma(p.alpha, p.beta);
1540 dist = Lognormal(p.mu, p.sigma);
1543 dist = Uniform(p.a, p.b);
1546 dist = Zipf(p.s, p.n);
1549 dist = Pareto(p.alpha, p.scale);
1552 % JSON: alpha = scale (getParam(1)), beta = shape (getParam(2))
1553 % Constructor: Weibull(shape, scale)
1554 dist = Weibull(p.beta, p.alpha);
1557 dist = Normal(p.mu, p.sigma);
1560 dist = Geometric(p.p);
1563 dist = Binomial(p.n, p.p);
1566 dist = Poisson(p.lambda);
1569 dist = Bernoulli(p.p);
1571 case 'DiscreteUniform'
1572 dist = DiscreteUniform(p.min, p.max);
1574 case 'DiscreteSampler'
1577 dist = DiscreteSampler(pv, xv);
1582 dist = Coxian(mu, phi);
1585 dist = MMPP2(p.lambda0, p.lambda1, p.sigma0, p.sigma1);
1588 % Try to load from file first
1589 if isfield(p, 'fileName') && exist(p.fileName, 'file') == 2
1590 dist = Replayer(p.fileName);
1593 % Fallback to APH if available
1594 if isfield(d, 'ph') && ~isempty(d.ph)
1598 if ~isvector(alpha), alpha = alpha(:)'; end
1599 dist = PH(alpha, T);
1602 % Fallback to Exp with stored mean
1604 if isfield(p, 'mean'), m = p.mean; end
1605 dist = Exp(1.0 / m);
1610% Prior distribution (mixture of alternatives with prior probabilities)
1611if strcmp(dtype, 'Prior')
1612 if isfield(d, 'distributions') && isfield(d, 'probabilities')
1613 altJsons = d.distributions;
1614 probs = d.probabilities;
1615 if ~iscell(altJsons)
1616 % jsondecode may return struct
array instead of cell
1617 altJsons = num2cell(altJsons);
1619 alts = cell(1, length(altJsons));
1620 for ai = 1:length(altJsons)
1621 alts{ai} = json2dist(altJsons{ai});
1624 dist = Prior(alts, probs);
1629% PH/APH representation
1630if isfield(d, 'ph
') && ~isempty(d.ph)
1634 alpha = alpha(:)'; % Ensure row vector (jsondecode returns column vectors)
1635 if strcmp(dtype,
'APH')
1636 dist = APH(alpha, T);
1638 dist = PH(alpha, T);
1644if isfield(d, '
map') && ~isempty(d.
map)
1653if isfield(d, 'fit') && ~isempty(d.fit)
1655 method = fit.method;
1661 dist = Exp(1.0 / m);
1665 dist = Exp(1.0 / m);
1668 case 'fitMeanAndSCV'
1673 dist = Erlang.fitMeanAndSCV(m, scv);
1675 dist = HyperExp.fitMeanAndSCV(m, scv);
1677 dist = Exp(1.0 / m);
1680 case 'fitMeanAndOrder'
1685 dist = Erlang.fitMeanAndOrder(m, order);
1687 dist = Exp(1.0 / m);
1698% =========================================================================
1699% Routing parser (handles comma keys in JSON)
1700% =========================================================================
1702function entries = parse_routing_keys(rawJson, class_map, node_map)
1703% Parse routing matrix from raw JSON text to handle keys with commas.
1704% Returns a cell
array of structs with fields:
1705% className1, className2, fromNode, toNode, prob
1708% Build reverse mapping: jsondecode-sanitized name -> original node name
1709% jsondecode uses matlab.lang.makeValidName which replaces spaces etc.
1710nodeNames = node_map.keys();
1711sanitized_map = containers.Map();
1712for ni = 1:length(nodeNames)
1713 origName = nodeNames{ni};
1714 sanitized = matlab.lang.makeValidName(origName);
1715 sanitized_map(sanitized) = origName;
1718classNames = class_map.keys();
1720% For each pair of
class names, try to find the corresponding key in the JSON
1721for ri = 1:length(classNames)
1722 for si = 1:length(classNames)
1723 cn1 = classNames{ri};
1724 cn2 = classNames{si};
1725 keyStr = [
'"', cn1,
',', cn2,
'"'];
1727 % Find
this key in the raw JSON
1728 pos = strfind(rawJson, keyStr);
1733 % For each occurrence, extract the nested from -> to -> prob structure
1734 for pidx = 1:length(pos)
1735 startPos = pos(pidx) + length(keyStr);
1736 % Skip whitespace and colon
1738 while idx <= length(rawJson) && (rawJson(idx) ==
' ' || rawJson(idx) ==
':' || rawJson(idx) == newline || rawJson(idx) == char(13) || rawJson(idx) == char(9))
1741 if idx > length(rawJson) || rawJson(idx) ~=
'{'
1744 % Extract the JSON
object using brace counting
1745 objStr = extract_json_object(rawJson, idx);
1749 % Parse the from -> to -> prob structure
1751 fromTo = jsondecode(objStr);
1752 fromNames = fieldnames(fromTo);
1753 for fi = 1:length(fromNames)
1754 fromField = fromNames{fi};
1755 toStruct = fromTo.(fromField);
1756 toNames = fieldnames(toStruct);
1757 % Resolve sanitized field names back to original node names
1758 if sanitized_map.isKey(fromField)
1759 fromName = sanitized_map(fromField);
1761 fromName = fromField;
1763 for ti = 1:length(toNames)
1764 toField = toNames{ti};
1765 prob = toStruct.(toField);
1766 if sanitized_map.isKey(toField)
1767 toName = sanitized_map(toField);
1771 % Verify names exist in the model
1772 if node_map.isKey(fromName) && node_map.isKey(toName)
1774 re.className1 = cn1;
1775 re.className2 = cn2;
1776 re.fromNode = fromName;
1779 entries{end+1} = re; %#ok<AGROW>
1784 % Skip
if parsing fails
1792function objStr = extract_json_object(str, startIdx)
1793% Extract a JSON
object string starting at startIdx (must be
'{').
1794if str(startIdx) ~=
'{'
1801for i = startIdx:length(str)
1812 inString = ~inString;
1821 objStr = str(startIdx:i);
1831% =========================================================================
1833% =========================================================================
1835function
id = str_to_sched_id(str)
1836% Map scheduling
string to SchedStrategy numeric ID.
1838 case 'INF',
id = SchedStrategy.INF;
1839 case 'FCFS',
id = SchedStrategy.FCFS;
1840 case 'LCFS',
id = SchedStrategy.LCFS;
1841 case 'LCFSPR',
id = SchedStrategy.LCFSPR;
1842 case 'PS',
id = SchedStrategy.PS;
1843 case 'DPS',
id = SchedStrategy.DPS;
1844 case 'GPS',
id = SchedStrategy.GPS;
1845 case 'SIRO',
id = SchedStrategy.SIRO;
1846 case 'RAND',
id = SchedStrategy.SIRO; % alias
1847 case 'SJF',
id = SchedStrategy.SJF;
1848 case 'LJF',
id = SchedStrategy.LJF;
1849 case 'SEPT',
id = SchedStrategy.SEPT;
1850 case 'LEPT',
id = SchedStrategy.LEPT;
1851 case 'HOL',
id = SchedStrategy.HOL;
1852 case 'FCFSPRIO',
id = SchedStrategy.FCFSPRIO;
1853 case 'FORK',
id = SchedStrategy.FORK;
1854 case 'EXT',
id = SchedStrategy.EXT;
1855 case 'REF',
id = SchedStrategy.REF;
1856 case 'POLLING',
id = SchedStrategy.POLLING;
1857 case 'PSPRIO',
id = SchedStrategy.PSPRIO;
1858 case 'DPSPRIO',
id = SchedStrategy.DPSPRIO;
1859 case 'GPSPRIO',
id = SchedStrategy.GPSPRIO;
1860 otherwise,
id = SchedStrategy.FCFS;
1865function
id = str_to_repl_id(str)
1866% Map replacement strategy
string to ReplacementStrategy numeric ID.
1868 case 'LRU',
id = ReplacementStrategy.LRU;
1869 case 'FIFO',
id = ReplacementStrategy.FIFO;
1870 case 'RR',
id = ReplacementStrategy.RR;
1871 case 'SFIFO',
id = ReplacementStrategy.SFIFO;
1872 otherwise,
id = ReplacementStrategy.LRU;
1877function
id = str_to_prectype(str)
1878% Map JAR precedence type
string to MATLAB ActivityPrecedenceType numeric ID.
1880 case 'pre',
id = ActivityPrecedenceType.PRE_SEQ;
1881 case 'pre-AND',
id = ActivityPrecedenceType.PRE_AND;
1882 case 'pre-OR',
id = ActivityPrecedenceType.PRE_OR;
1883 case 'post',
id = ActivityPrecedenceType.POST_SEQ;
1884 case 'post-AND',
id = ActivityPrecedenceType.POST_AND;
1885 case 'post-OR',
id = ActivityPrecedenceType.POST_OR;
1886 case 'post-LOOP',
id = ActivityPrecedenceType.POST_LOOP;
1887 case 'post-CACHE',
id = ActivityPrecedenceType.POST_CACHE;
1888 otherwise,
id = ActivityPrecedenceType.PRE_SEQ;
1893function
id = str_to_droprule(str)
1894% Map drop rule
string to DropStrategy numeric ID.
1896 case 'drop',
id = DropStrategy.DROP;
1897 case 'waitingQueue',
id = DropStrategy.WAITQ;
1898 case 'blockingAfterService',
id = DropStrategy.BAS;
1899 case 'retrial',
id = DropStrategy.RETRIAL;
1900 case 'retrialWithLimit',
id = DropStrategy.RETRIAL_WITH_LIMIT;
1901 otherwise,
id = DropStrategy.WAITQ;
1906function c = cellify_string_array(arr)
1907% Convert a JSON
string array (which may be decoded as a
char, cell, or
1908%
struct array) into a cell
array of character vectors.
1916 % jsondecode can
return a
struct array or char matrix for string arrays